From f57ecc98004709cb817f2aec3c760828d512588e Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Sun, 15 Mar 2020 01:06:09 +0100 Subject: [PATCH] Improve and optimize memset (see #47) --- .../AbstractStatementPreprocessor.scala | 83 +++++++++++++++++ .../millfork/compiler/MacroExpander.scala | 2 + .../m6809/M6809StatementCompiler.scala | 4 +- .../mos/MosBulkMemoryOperations.scala | 53 ++++++++--- .../compiler/mos/MosStatementCompiler.scala | 17 ++-- .../z80/Z80BulkMemoryOperations.scala | 65 +++++++++++--- .../compiler/z80/Z80StatementCompiler.scala | 5 +- .../z80/Z80StatementPreprocessor.scala | 4 +- src/main/scala/millfork/env/Constant.scala | 21 +++++ src/main/scala/millfork/env/Environment.scala | 42 +++++++++ src/main/scala/millfork/env/Thing.scala | 5 ++ src/main/scala/millfork/node/Node.scala | 38 ++++++++ .../scala/millfork/test/MemBulkSuite.scala | 48 ++++++++++ .../scala/millfork/test/MemsetSuite.scala | 88 ++++++++++++++++--- 14 files changed, 426 insertions(+), 49 deletions(-) create mode 100644 src/test/scala/millfork/test/MemBulkSuite.scala diff --git a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala index 4e4be85a..a0598ebe 100644 --- a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala +++ b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala @@ -185,6 +185,62 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte val (b, _) = optimizeStmts(body, Map()) ForEachStatement(v, a, b).pos(pos) -> Map() case f@ForStatement(v, st, en, dir, body) => + + // detect a memset + f.body match { + case List(Assignment(target@IndexedExpression(pointy, index), source)) => + val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source) + val targetType = AbstractExpressionCompiler.getExpressionType(ctx, target) + if ( + !env.isVolatile(VariableExpression(pointy)) && + !env.isVolatile(index) && + !env.isVolatile(source) && + !env.isVolatile(f.end) && + index.getAllIdentifiers.forall(iv => !ctx.env.overlapsVariable(iv, source)) && + !env.overlapsVariable(pointy, source) && + !env.overlapsVariable(pointy, index) && + !env.overlapsVariable(f.variable, source) && + !env.overlapsVariable(f.variable, f.start) && + !env.overlapsVariable(f.variable, f.end) && + source.isPure && + sourceType.size == 1 && + targetType.size == 1 && + sourceType.isAssignableTo(targetType) + ) { + val sizeExpr = f.direction match { + case ForDirection.DownTo => + f.start #-# f.end #+# 1 + case ForDirection.To | ForDirection.ParallelTo => + f.end #-# f.start #+# 1 + case ForDirection.Until | ForDirection.ParallelUntil => + f.end #-# f.start + } + val w = env.get[Type]("word") + env.eval(sizeExpr) match { + case Some(size) => + val startOpt = optimizeExpr(f.start, Map()) + val sourceOpt = optimizeExpr(source, Map()) + (env.getPointy(pointy), env.evalVariableAndConstantSubParts(index)) match { + case (array: ConstantPointy, (Some(VariableExpression(i)), offset)) if i == f.variable => + // for i,start,until,end { array[i+offset] = source } + // println(s"Detected memset via array $array and index $i") + return MemsetStatement(startOpt #+# GeneratedConstantExpression(array.value + offset, w), size, sourceOpt, f.direction, Some(f)).pos(pos) -> Map() + case (pointer, (Some(VariableExpression(i)), offset)) if i == f.variable => + // for i,start,until,end { array[i+offset] = source } + // println(s"Detected memset via pointer $pointer and index $i") + return MemsetStatement(startOpt #+# VariableExpression(pointy) #+# GeneratedConstantExpression(offset, w), size, sourceOpt, f.direction, Some(f)).pos(pos) -> Map() + case (_, (None, offset)) if pointy == f.variable => + // for pointy,start,until,end { pointy[offset] = source } + // println(s"Detected memset via pointer $pointy alone") + return MemsetStatement(startOpt #+# GeneratedConstantExpression(offset, w), size, sourceOpt, f.direction, Some(f)).pos(pos) -> Map() + case _ => + } + case _ => + } + } + case _ => + } + maybeOptimizeForStatement(f) match { case Some(x) => x case None => @@ -586,4 +642,31 @@ object AbstractStatementPreprocessor { "==", "!=", "<", ">", ">=", "<=", "not", "hi", "lo", "nonet", "sizeof" ) + + def mightBeMemset(ctx: CompilationContext, f: ForStatement): Boolean = { + val env = ctx.env + f.body match { + case List(Assignment(target@IndexedExpression(pointy, index), source)) => + val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source) + val targetType = AbstractExpressionCompiler.getExpressionType(ctx, target) + if ( + source.isPure && + index.isPure && + sourceType.size == 1 && + targetType.size == 1 && + sourceType.isAssignableTo(targetType)&& + !env.isVolatile(VariableExpression(pointy)) && + !env.isVolatile(index) && + !env.isVolatile(source) && + !env.isVolatile(f.end) && + index.getAllIdentifiers.forall(iv => !ctx.env.overlapsVariable(iv, source)) && + !env.overlapsVariable(pointy, source) && + !env.overlapsVariable(pointy, index) && + !env.overlapsVariable(f.variable, source) && + !env.overlapsVariable(f.variable, f.start) && + !env.overlapsVariable(f.variable, f.end) + ) env.eval(f.end #-# f.start).isDefined else false + case _ => false + } + } } \ No newline at end of file diff --git a/src/main/scala/millfork/compiler/MacroExpander.scala b/src/main/scala/millfork/compiler/MacroExpander.scala index 108599a3..981ccd4d 100644 --- a/src/main/scala/millfork/compiler/MacroExpander.scala +++ b/src/main/scala/millfork/compiler/MacroExpander.scala @@ -38,6 +38,7 @@ abstract class MacroExpander[T <: AbstractCode] { case WhileStatement(c, b, i, n) => WhileStatement(f(c), b.map(gx), i.map(gx), n) case DoWhileStatement(b, i, c, n) => DoWhileStatement(b.map(gx), i.map(gx), f(c), n) case ForStatement(v, start, end, dir, body) => ForStatement(h(v), f(start), f(end), dir, body.map(gx)) + case MemsetStatement(start, size, value, dir, original) => MemsetStatement(f(start), size, f(value), dir, original.map(gx).asInstanceOf[Option[ForStatement]]) case IfStatement(c, t, e) => IfStatement(f(c), t.map(gx), e.map(gx)) case s: Z80AssemblyStatement => s.copy(expression = f(s.expression), offsetExpression = s.offsetExpression.map(f)) case s: MosAssemblyStatement => s.copy(expression = f(s.expression)) @@ -76,6 +77,7 @@ abstract class MacroExpander[T <: AbstractCode] { case WhileStatement(c, b, i, n) => WhileStatement(f(c), b.map(gx), i.map(gx), n) case DoWhileStatement(b, i, c, n) => DoWhileStatement(b.map(gx), i.map(gx), f(c), n) case ForStatement(v, start, end, dir, body) => ForStatement(h(v), f(start), f(end), dir, body.map(gx)) + case MemsetStatement(start, size, value, dir, original) => MemsetStatement(f(start), size, f(value), dir, original.map(gx).asInstanceOf[Option[ForStatement]]) case IfStatement(c, t, e) => IfStatement(f(c), t.map(gx), e.map(gx)) case s: Z80AssemblyStatement => s.copy(expression = f(s.expression), offsetExpression = s.offsetExpression.map(f)) case s: MosAssemblyStatement => s.copy(expression = f(s.expression)) diff --git a/src/main/scala/millfork/compiler/m6809/M6809StatementCompiler.scala b/src/main/scala/millfork/compiler/m6809/M6809StatementCompiler.scala index bd4feff0..8c62482d 100644 --- a/src/main/scala/millfork/compiler/m6809/M6809StatementCompiler.scala +++ b/src/main/scala/millfork/compiler/m6809/M6809StatementCompiler.scala @@ -3,7 +3,7 @@ package millfork.compiler.m6809 import millfork.assembly.BranchingOpcodeMapping import millfork.assembly.m6809.{MLine, NonExistent} import millfork.compiler.{AbstractCompiler, AbstractExpressionCompiler, AbstractStatementCompiler, BranchSpec, CompilationContext} -import millfork.node.{Assignment, BlackHoleExpression, BreakStatement, ContinueStatement, DoWhileStatement, ExecutableStatement, Expression, ExpressionStatement, ForEachStatement, ForStatement, FunctionCallExpression, IfStatement, M6809AssemblyStatement, ReturnDispatchStatement, ReturnStatement, VariableExpression, WhileStatement} +import millfork.node.{Assignment, BlackHoleExpression, BreakStatement, ContinueStatement, DoWhileStatement, ExecutableStatement, Expression, ExpressionStatement, ForEachStatement, ForStatement, FunctionCallExpression, IfStatement, M6809AssemblyStatement, MemsetStatement, ReturnDispatchStatement, ReturnStatement, VariableExpression, WhileStatement} import millfork.assembly.m6809.MOpcode._ import millfork.env.{BooleanType, ConstantBooleanType, FatBooleanType, Label, ThingInMemory} @@ -75,6 +75,8 @@ object M6809StatementCompiler extends AbstractStatementCompiler[MLine] { compileDoWhileStatement(ctx, s) case s:ForStatement => compileForStatement(ctx, s) + case s:MemsetStatement => + compile(ctx, s.original.get) case s:ForEachStatement => compileForEachStatement(ctx, s) case s:BreakStatement => diff --git a/src/main/scala/millfork/compiler/mos/MosBulkMemoryOperations.scala b/src/main/scala/millfork/compiler/mos/MosBulkMemoryOperations.scala index 9cefa4c0..d092457c 100644 --- a/src/main/scala/millfork/compiler/mos/MosBulkMemoryOperations.scala +++ b/src/main/scala/millfork/compiler/mos/MosBulkMemoryOperations.scala @@ -2,7 +2,7 @@ package millfork.compiler.mos import millfork.CompilationFlag import millfork.assembly.mos.{AddrMode, AssemblyLine, AssemblyLine0, Opcode} -import millfork.compiler.{AbstractExpressionCompiler, BranchSpec, CompilationContext} +import millfork.compiler.{AbstractExpressionCompiler, AbstractStatementPreprocessor, BranchSpec, CompilationContext} import millfork.env.{ConstantPointy, Label, MemoryAddressConstant, MemoryVariable, NumericConstant, RelativeVariable, StackVariablePointy, Type, Variable, VariableAllocationMethod, VariableInMemory, VariablePointy} import millfork.node._ import millfork.assembly.mos.Opcode._ @@ -14,11 +14,29 @@ object MosBulkMemoryOperations { def compileMemset(ctx: CompilationContext, target: IndexedExpression, source: Expression, f: ForStatement): List[AssemblyLine] = { if (ctx.options.zpRegisterSize < 2 || - target.name != f.variable || - target.index.containsVariable(f.variable) || - !target.index.isPure || - f.direction == ForDirection.DownTo) return MosStatementCompiler.compileForStatement(ctx, f)._1 - ctx.env.getPointy(target.name) + !AbstractStatementPreprocessor.mightBeMemset(ctx, f) || + f.direction == ForDirection.DownTo) { + return MosStatementCompiler.compileForStatement(ctx, f)._1 + } + val pointy = ctx.env.getPointy(target.name) + if (pointy.elementType.size != 1) { + return MosStatementCompiler.compileForStatement(ctx, f)._1 + } + val w = ctx.env.get[Type]("word") + val startExpr = () match { + case _ if target.name == f.variable && !ctx.env.overlapsVariable(target.name, target.index) => + f.start #+# target.index + case _ if target.name != f.variable && !ctx.env.overlapsVariable(target.name, source) => + (pointy, ctx.env.evalVariableAndConstantSubParts(target.index)) match { + case (pty: ConstantPointy, (Some(VariableExpression(n)), offset)) if n == f.variable => + f.start #+# GeneratedConstantExpression(pty.value, w) #+# GeneratedConstantExpression(offset, w) + case bad => + val badd = bad + return MosStatementCompiler.compileForStatement(ctx, f)._1 + } + case _ => + return MosStatementCompiler.compileForStatement(ctx, f)._1 + } val sizeExpr = f.direction match { case ForDirection.DownTo => f.start #-# f.end #+# 1 @@ -27,17 +45,24 @@ object MosBulkMemoryOperations { case ForDirection.Until | ForDirection.ParallelUntil => f.end #-# f.start } - val reg = ctx.env.get[VariableInMemory]("__reg.loword") - val w = ctx.env.get[Type]("word") val size = ctx.env.eval(sizeExpr) match { case Some(c) => c.quickSimplify case _ => return MosStatementCompiler.compileForStatement(ctx, f)._1 } + compileMemset(ctx, MemsetStatement(startExpr, size, source, f.direction, Some(f))) + } + + def compileMemset(ctx: CompilationContext, m: MemsetStatement): List[AssemblyLine] = { + if (m.direction == ForDirection.DownTo) { + return MosStatementCompiler.compileForStatement(ctx, m.original.get)._1 + } + val w = ctx.env.get[Type]("word") + val reg = ctx.env.get[VariableInMemory]("__reg.loword") val useTwoRegs = ctx.options.flag(CompilationFlag.OptimizeForSpeed) && ctx.options.zpRegisterSize >= 4 val loadReg = if (useTwoRegs) { import millfork.assembly.mos.AddrMode._ - val first = MosExpressionCompiler.compile(ctx, f.start #+# target.index, Some(w -> reg), BranchSpec.None) + val first = MosExpressionCompiler.compile(ctx, m.start, Some(w -> reg), BranchSpec.None) first ++ (first match { case List(AssemblyLine0(LDA, Immediate, l), AssemblyLine0(LDA, ZeroPage, r0), AssemblyLine0(LDA, Immediate, h), AssemblyLine0(LDA, ZeroPage, r1)) if (r1-r0).quickSimplify.isProvably(1) => @@ -57,15 +82,15 @@ object MosBulkMemoryOperations { AssemblyLine.immediate(ADC, 0), AssemblyLine.zeropage(STA, reg, 3)) }) - } else MosExpressionCompiler.compile(ctx, f.start #+# target.index, Some(w -> reg), BranchSpec.None) + } else MosExpressionCompiler.compile(ctx, m.start, Some(w -> reg), BranchSpec.None) - val loadSource = MosExpressionCompiler.compileToA(ctx, source) + val loadSource = MosExpressionCompiler.compileToA(ctx, m.value) val loadAll = if (MosExpressionCompiler.changesZpreg(loadSource, 0) || MosExpressionCompiler.changesZpreg(loadSource, 1)) { loadSource ++ MosExpressionCompiler.preserveRegisterIfNeeded(ctx, MosRegister.A, loadReg) } else { loadReg ++ loadSource } - val wholePageCount = size.hiByte.quickSimplify + val wholePageCount = m.size.quickSimplify.hiByte.quickSimplify def fillOnePage: List[AssemblyLine] = { val label = ctx.nextLabel("ms") @@ -141,7 +166,7 @@ object MosBulkMemoryOperations { AssemblyLine.relative(BNE, labelX), AssemblyLine.label(labelXSkip)) } - val restSize = size.loByte.quickSimplify + val restSize = m.size.quickSimplify.loByte.quickSimplify val setRest = restSize match { case NumericConstant(0, _) => Nil case NumericConstant(1, _) => @@ -153,7 +178,7 @@ object MosBulkMemoryOperations { case _ => val label = ctx.nextLabel("ms") val labelSkip = ctx.nextLabel("ms") - if (f.direction == ForDirection.ParallelUntil) { + if (m.direction == ForDirection.ParallelUntil) { List( AssemblyLine.immediate(LDY, restSize), AssemblyLine.relative(BEQ, labelSkip), diff --git a/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala b/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala index 7fd15d32..0295a446 100644 --- a/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala @@ -300,18 +300,25 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] { compileWhileStatement(ctx, s) case s: DoWhileStatement => compileDoWhileStatement(ctx, s) - case f@ForStatement(variable, _, _, _, List(Assignment(target: IndexedExpression, source: Expression))) if !source.containsVariable(variable) => + case f:MemsetStatement => + MosBulkMemoryOperations.compileMemset(ctx, f) -> Nil + case f@ForStatement(variable, _, _, _, List(Assignment(target: IndexedExpression, source: Expression))) if !ctx.env.overlapsVariable(variable, source) => MosBulkMemoryOperations.compileMemset(ctx, target, source, f) -> Nil case f@ForStatement(variable, start, end, _, List(ExpressionStatement( - FunctionCallExpression(operator@("+=" | "-=" | "+'=" | "-'=" | "|=" | "^=" | "&="), List(target: VariableExpression, source)) - ))) if !target.containsVariable(variable) && !source.containsVariable(variable) && !start.containsVariable(target.name) && !end.containsVariable(target.name) => + FunctionCallExpression(operator@("+=" | "-=" | "+'=" | "-'=" | "|=" | "^=" | "&="), List(target: VariableExpression, source)) + ))) if !ctx.env.overlapsVariable(variable, source) && + !ctx.env.overlapsVariable(variable, target) && + !ctx.env.overlapsVariable(target.name, start) && + !ctx.env.overlapsVariable(target.name, end) => MosBulkMemoryOperations.compileFold(ctx, target, operator, source, f) match { case Some(x) => x -> Nil case None => compileForStatement(ctx, f) } case f@ForStatement(variable, start, end, _, List(ExpressionStatement( - FunctionCallExpression(operator@("+=" | "-=" | "<<=" | ">>="), List(target: IndexedExpression, source)) - ))) if !source.containsVariable(variable) && !start.containsVariable(target.name) && !end.containsVariable(target.name) && target.name != variable => + FunctionCallExpression(operator@("+=" | "-=" | "<<=" | ">>="), List(target: IndexedExpression, source)) + ))) if !ctx.env.overlapsVariable(variable, source) && + !ctx.env.overlapsVariable(target.name, start) && + !ctx.env.overlapsVariable(target.name, end) && target.name != variable => MosBulkMemoryOperations.compileMemmodify(ctx, target, operator, source, f) match { case Some(x) => x -> Nil case None => compileForStatement(ctx, f) diff --git a/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala b/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala index a5a5e56c..72bbaad2 100644 --- a/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala +++ b/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala @@ -3,7 +3,7 @@ package millfork.compiler.z80 import millfork.CompilationFlag import millfork.assembly.Elidability import millfork.assembly.z80._ -import millfork.compiler.CompilationContext +import millfork.compiler.{AbstractStatementPreprocessor, CompilationContext} import millfork.env._ import millfork.node._ import millfork.assembly.z80.ZOpcode._ @@ -20,7 +20,7 @@ object Z80BulkMemoryOperations { * Compiles loops like for i,a,until,b { p[i] = q[i] } */ def compileMemcpy(ctx: CompilationContext, target: IndexedExpression, source: IndexedExpression, f: ForStatement): List[ZLine] = { - val sourceOffset = removeVariableOnce(f.variable, source.index).getOrElse(return compileForStatement(ctx, f)._1) + val sourceOffset = removeVariableOnce(ctx, f.variable, source.index).getOrElse(return compileForStatement(ctx, f)._1) if (!sourceOffset.isPure) return compileForStatement(ctx, f)._1 val sourceIndexExpression = sourceOffset #+# f.start val calculateSource = Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(source.name, sourceIndexExpression).pos(source.position), forWriting = false) @@ -43,6 +43,9 @@ object Z80BulkMemoryOperations { * where a is an arbitrary expression independent of i */ def compileMemset(ctx: CompilationContext, target: IndexedExpression, source: Expression, f: ForStatement): List[ZLine] = { + if (f.direction == ForDirection.DownTo || + !AbstractStatementPreprocessor.mightBeMemset(ctx, f)) return Z80StatementCompiler.compileForStatement(ctx, f)._1 + val loadA = Z80ExpressionCompiler.stashHLIfChanged(ctx, Z80ExpressionCompiler.compileToA(ctx, source)) :+ ZLine.ld8(ZRegister.MEM_HL, ZRegister.A) def compileForZ80(targetOffset: Expression): List[ZLine] = { @@ -81,12 +84,12 @@ object Z80BulkMemoryOperations { } if (ctx.options.flag(CompilationFlag.EmitZ80Opcodes)) { - removeVariableOnce(f.variable, target.index) match { + removeVariableOnce(ctx, f.variable, target.index) match { case Some(targetOffset) if targetOffset.isPure => return compileForZ80(targetOffset) case _ => } - if (target.isPure && target.name == f.variable && !target.index.containsVariable(f.variable)) { + if (target.isPure && target.name == f.variable && !ctx.env.overlapsVariable(f.variable, target.index)) { return compileForZ80(target.index) } } @@ -100,12 +103,46 @@ object Z80BulkMemoryOperations { ) } + def compileMemset(ctx: CompilationContext, f: MemsetStatement): List[ZLine] = { + if (ctx.options.flag(CompilationFlag.EmitZ80Opcodes)) { + val w = ctx.env.get[Type]("word") + val loadA = Z80ExpressionCompiler.stashHLIfChanged(ctx, Z80ExpressionCompiler.compileToA(ctx, f.value)) :+ ZLine.ld8(ZRegister.MEM_HL, ZRegister.A) + val startingAdress = f.direction match { + case ForDirection.DownTo => f.start #+# GeneratedConstantExpression(f.size, w) #-# 1 + case _ => f.start + } + val calculateAddress = Z80ExpressionCompiler.compileToHL(ctx, startingAdress) + val calculateSize = List(ZLine.ldImm16(ZRegister.BC, f.size - 1)) + val (incOp, ldOp) = f.direction match { + case ForDirection.DownTo => DEC_16 -> LDDR + case _ => INC_16 -> LDIR + } + val loadFirstValue = ctx.env.eval(f.value) match { + case Some(c) => List(ZLine.ldImm8(ZRegister.MEM_HL, c)) + case _ => Z80ExpressionCompiler.stashBCIfChanged(ctx, loadA) + } + val loadDE = calculateAddress match { + case List(ZLine0(ZOpcode.LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), c)) => + if (incOp == DEC_16) List(ZLine.ldImm16(ZRegister.DE, (c - 1).quickSimplify)) + else List(ZLine.ldImm16(ZRegister.DE, (c + 1).quickSimplify)) + case _ => List( + ZLine.ld8(ZRegister.D, ZRegister.H), + ZLine.ld8(ZRegister.E, ZRegister.L), + ZLine.register(incOp, ZRegister.DE)) + } + calculateAddress ++ calculateSize ++ loadFirstValue ++ loadDE :+ ZLine.implied(ldOp) + } else { + // go to the generic handler: + Z80StatementCompiler.compile(ctx, f.original.get)._1 + } + } + /** * Compiles loops like for i,a,until,b { target[i] = z }, * where z is an expression depending on source[i] */ def compileMemtransform(ctx: CompilationContext, target: IndexedExpression, operator: String, source: Expression, f: ForStatement): List[ZLine] = { - val c = determineExtraLoopRegister(ctx, f, source.containsVariable(f.variable)) + val c = determineExtraLoopRegister(ctx, f, ctx.env.overlapsVariable(f.variable, source)) val load = buildMemtransformLoader(ctx, ZRegister.MEM_HL, f.variable, operator, source, c.loopRegister).getOrElse(return compileForStatement(ctx, f)._1) import scala.util.control.Breaks._ breakable{ @@ -130,9 +167,9 @@ object Z80BulkMemoryOperations { target2: IndexedExpression, operator2: String, source2: Expression, f: ForStatement): List[ZLine] = { import scala.util.control.Breaks._ - val c = determineExtraLoopRegister(ctx, f, source1.containsVariable(f.variable) || source2.containsVariable(f.variable)) - val target1Offset = removeVariableOnce(f.variable, target2.index).getOrElse(return compileForStatement(ctx, f)._1) - val target2Offset = removeVariableOnce(f.variable, target2.index).getOrElse(return compileForStatement(ctx, f)._1) + val c = determineExtraLoopRegister(ctx, f, ctx.env.overlapsVariable(f.variable, source1) || ctx.env.overlapsVariable(f.variable, source2)) + val target1Offset = removeVariableOnce(ctx, f.variable, target2.index).getOrElse(return compileForStatement(ctx, f)._1) + val target2Offset = removeVariableOnce(ctx, f.variable, target2.index).getOrElse(return compileForStatement(ctx, f)._1) val target1IndexExpression = if (c.countDownDespiteSyntax) { target1Offset #+# f.end #-# 1 } else { @@ -397,7 +434,9 @@ object Z80BulkMemoryOperations { extraAddressCalculations: Boolean => (List[ZLine], List[ZLine]), loadA: ZOpcode.Value => List[ZLine], z80Bulk: Boolean => Option[ZOpcode.Value]): List[ZLine] = { - val targetOffset = removeVariableOnce(f.variable, target.index).getOrElse(return compileForStatement(ctx, f)._1) + val pointy = ctx.env.getPointy(target.name) + if (pointy.elementType.size > 1) return Z80StatementCompiler.compileForStatement(ctx, f)._1 + val targetOffset = removeVariableOnce(ctx, f.variable, target.index).getOrElse(return compileForStatement(ctx, f)._1) if (!targetOffset.isPure) return compileForStatement(ctx, f)._1 val indexVariableSize = ctx.env.get[Variable](f.variable).typ.size val wrapper = createForLoopPreconditioningIfStatement(ctx, f) @@ -469,14 +508,14 @@ object Z80BulkMemoryOperations { Nil))._1 } - private def removeVariableOnce(variable: String, expr: Expression): Option[Expression] = { + private def removeVariableOnce(ctx: CompilationContext, variable: String, expr: Expression): Option[Expression] = { expr match { case VariableExpression(i) => if (i == variable) Some(LiteralExpression(0, 1)) else None case SumExpression(exprs, false) => - if (exprs.count(_._2.containsVariable(variable)) == 1) { + if (exprs.count(e => ctx.env.overlapsVariable(variable, e._2)) == 1) { Some(SumExpression(exprs.map { - case (false, e) => false -> (if (e.containsVariable(variable)) removeVariableOnce(variable, e).getOrElse(return None) else e) - case (true, e) => if (e.containsVariable(variable)) return None else true -> e + case (false, e) => if (ctx.env.overlapsVariable(variable, e)) false -> removeVariableOnce(ctx, variable, e).getOrElse(return None) else false -> e + case (true, e) => if (ctx.env.overlapsVariable(variable, e)) return None else true -> e }, decimal = false)) } else None case _ => None diff --git a/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala index c3dc2a09..564337cf 100644 --- a/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala @@ -140,10 +140,13 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] { case s: ReturnDispatchStatement => Z80ReturnDispatch.compile(ctx, s) -> Nil + case f:MemsetStatement => + Z80BulkMemoryOperations.compileMemset(ctx, f) -> Nil + case f@ForStatement(_, _, _, _, List(Assignment(target: IndexedExpression, source: IndexedExpression))) => Z80BulkMemoryOperations.compileMemcpy(ctx, target, source, f) -> Nil - case f@ForStatement(variable, _, _, _, List(Assignment(target: IndexedExpression, source: Expression))) if !source.containsVariable(variable) => + case f@ForStatement(variable, _, _, _, List(Assignment(target: IndexedExpression, source: Expression))) if ctx.env.overlapsVariable(variable, source) => Z80BulkMemoryOperations.compileMemset(ctx, target, source, f) -> Nil case f@ForStatement(variable, _, _, _, List(ExpressionStatement(FunctionCallExpression( diff --git a/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala b/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala index edb59305..8b6926ee 100644 --- a/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala +++ b/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala @@ -47,8 +47,8 @@ class Z80StatementPreprocessor(ctx: CompilationContext, statements: List[Executa if (!optimize) return None if (ctx.env.eval(f.start).isEmpty) return None if (f.variable.contains(".")) return None - if (f.start.containsVariable(f.variable)) return None - if (f.end.containsVariable(f.variable)) return None + if (ctx.env.overlapsVariable(f.variable, f.start)) return None + if (ctx.env.overlapsVariable(f.variable, f.end)) return None val indexVariable = env.get[Variable](f.variable) if (indexVariable.typ.size != 1) return None if (indexVariable.isVolatile) return None diff --git a/src/main/scala/millfork/env/Constant.scala b/src/main/scala/millfork/env/Constant.scala index 22b4006c..01b07fec 100644 --- a/src/main/scala/millfork/env/Constant.scala +++ b/src/main/scala/millfork/env/Constant.scala @@ -20,6 +20,7 @@ import millfork.node.Position sealed trait Constant { + def toIntelString: String def isQuiteNegative: Boolean = false @@ -127,6 +128,8 @@ sealed trait Constant { } final def succ: Constant = (this + 1).quickSimplify + + def rootThingName: String } case class AssertByte(c: Constant) extends Constant { @@ -149,6 +152,7 @@ case class AssertByte(c: Constant) extends Constant { override def fitsInto(typ: Type): Boolean = true override def toIntelString: String = c.toIntelString + override def rootThingName: String = c.rootThingName } case class StructureConstant(typ: StructType, fields: List[Constant]) extends Constant { @@ -192,6 +196,7 @@ case class StructureConstant(typ: StructType, fields: List[Constant]) extends Co } Constant.Zero } + override def rootThingName: String = "?" } case class UnexpandedConstant(name: String, requiredSize: Int) extends Constant { @@ -202,6 +207,8 @@ case class UnexpandedConstant(name: String, requiredSize: Int) extends Constant override def toIntelString: String = name override def refersTo(name: String): Boolean = name == this.name + + override def rootThingName: String = "?" } case class NumericConstant(value: Long, requiredSize: Int) extends Constant { @@ -284,6 +291,8 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant { NumericConstant(actualBits, typ.size) } } + + override def rootThingName: String = "" } case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant { @@ -322,6 +331,8 @@ case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant { override def isRelatedTo(v: Thing): Boolean = thing.name == v.name override def refersTo(name: String): Boolean = name == thing.name + + override def rootThingName: String = thing.rootName } case class SubbyteConstant(base: Constant, index: Int) extends Constant { @@ -360,6 +371,8 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant { override def isRelatedTo(v: Thing): Boolean = base.isRelatedTo(v) override def refersTo(name: String): Boolean = base.refersTo(name) + + override def rootThingName: String = base.rootThingName } object MathOperator extends Enumeration { @@ -681,4 +694,12 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co } result } + + override def rootThingName: String = (lhs.rootThingName, rhs.rootThingName) match { + case ("?", _) => "?" + case (_, "?") => "?" + case ("", x) => x + case (x, "") => x + case _ => "?" + } } diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 89616604..820c5207 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -2228,6 +2228,48 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa }.toMap ++ parent.map(_.getAliases).getOrElse(Map.empty) } + def isVolatile(target: Expression): Boolean = { + if (eval(target).isDefined) return false + target match { + case _: LiteralExpression => false + case _: GeneratedConstantExpression => false + case e: VariableExpression => maybeGet[Thing](e.name) match { + case Some(v: Variable) => v.isVolatile + case Some(v: MfArray) => true // TODO: all arrays assumed volatile for now + case Some(_: Constant) => false + case Some(_: Type) => false + case _ => true // TODO: ? + } + case e: FunctionCallExpression => e.expressions.exists(isVolatile) + case e: IndexedExpression => isVolatile(VariableExpression(e.name)) || isVolatile(e.index) + case _ => true + } + } + + def overlapsVariable(variable: String, expr: Expression): Boolean = { + if (eval(expr).isDefined) return false + if (expr.containsVariable(variable)) return true + val varRootName = get[Thing](variable).rootName + if (varRootName == "?") return true + if (varRootName == "") return false + overlapsVariableImpl(varRootName, expr) + } + + private def overlapsVariableImpl(varRootName: String, expr: Expression): Boolean = { + expr match { + case _: LiteralExpression => false + case _: GeneratedConstantExpression => false + case e: VariableExpression => maybeGet[Thing](e.name) match { + case Some(t) => + val rootName = t.rootName + rootName == "?" || rootName == varRootName + case _ => true // TODO: ? + } + case e: FunctionCallExpression => e.expressions.exists(x => overlapsVariableImpl(varRootName, x)) + case e: IndexedExpression => overlapsVariableImpl(varRootName, VariableExpression(e.name)) || overlapsVariableImpl(varRootName, e.index) + case _ => true + } + } } object Environment { diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 7b09ac0b..01936242 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -7,6 +7,7 @@ import millfork.output.{MemoryAlignment, NoAlignment} sealed trait Thing { def name: String + def rootName: String = name } case class Alias(name: String, target: String, deprecated: Boolean = false) extends Thing @@ -338,6 +339,8 @@ case class RelativeArray(name: String, address: Constant, elementCount: Int, dec override def zeropage: Boolean = false override def sizeInBytes: Int = elementCount * elementType.size + + override def rootName: String = address.rootThingName } case class InitializedArray(name: String, address: Option[Constant], contents: Seq[Expression], declaredBank: Option[String], indexType: VariableType, elementType: VariableType, override val readOnly: Boolean, override val alignment: MemoryAlignment) extends MfArray with PreallocableThing { @@ -357,6 +360,8 @@ case class InitializedArray(name: String, address: Option[Constant], contents: S case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean, declaredBank: Option[String], override val isVolatile: Boolean) extends VariableInMemory { override def toAddress: Constant = address + + override def rootName: String = address.rootThingName } sealed trait MangledFunction extends CallableThing { diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index 70af40a7..920bfc2b 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -41,6 +41,7 @@ sealed trait Expression extends Node { def getPointies: Seq[String] def isPure: Boolean def getAllIdentifiers: Set[String] + def prettyPrint: String def #+#(smallInt: Int): Expression = if (smallInt == 0) this else (this #+# LiteralExpression(smallInt, 1).pos(this.position)).pos(this.position) def #+#(that: Expression): Expression = that match { @@ -63,6 +64,7 @@ case class ConstantArrayElementExpression(constant: Constant) extends Expression override def getPointies: Seq[String] = Seq.empty override def isPure: Boolean = true override def getAllIdentifiers: Set[String] = Set.empty + override def prettyPrint: String = constant.toString } case class LiteralExpression(value: Long, requiredSize: Int) extends Expression { @@ -73,6 +75,7 @@ case class LiteralExpression(value: Long, requiredSize: Int) extends Expression override def getPointies: Seq[String] = Seq.empty override def isPure: Boolean = true override def getAllIdentifiers: Set[String] = Set.empty + override def prettyPrint: String = "$" + value.toHexString } case class TextLiteralExpression(characters: List[Expression]) extends Expression { @@ -83,6 +86,7 @@ case class TextLiteralExpression(characters: List[Expression]) extends Expressio override def getPointies: Seq[String] = Seq.empty override def isPure: Boolean = true override def getAllIdentifiers: Set[String] = Set.empty + override def prettyPrint: String = characters.map(_.prettyPrint).mkString("[", ",", "]") } case class GeneratedConstantExpression(value: Constant, typ: Type) extends Expression { @@ -93,6 +97,7 @@ case class GeneratedConstantExpression(value: Constant, typ: Type) extends Expre override def getPointies: Seq[String] = Seq.empty override def isPure: Boolean = true override def getAllIdentifiers: Set[String] = Set.empty + override def prettyPrint: String = value.toString } case class BooleanLiteralExpression(value: Boolean) extends Expression { @@ -103,6 +108,7 @@ case class BooleanLiteralExpression(value: Boolean) extends Expression { override def getPointies: Seq[String] = Seq.empty override def isPure: Boolean = true override def getAllIdentifiers: Set[String] = Set.empty + override def prettyPrint: String = value.toString } sealed trait LhsExpression extends Expression @@ -115,6 +121,7 @@ case object BlackHoleExpression extends LhsExpression { override def getPointies: Seq[String] = Seq.empty override def isPure: Boolean = true override def getAllIdentifiers: Set[String] = Set.empty + override def prettyPrint: String = "(_|_)" } case class SeparateBytesExpression(hi: Expression, lo: Expression) extends LhsExpression { @@ -134,6 +141,7 @@ case class SeparateBytesExpression(hi: Expression, lo: Expression) extends LhsEx override def getPointies: Seq[String] = hi.getPointies ++ lo.getPointies override def isPure: Boolean = hi.isPure && lo.isPure override def getAllIdentifiers: Set[String] = hi.getAllIdentifiers ++ lo.getAllIdentifiers + override def prettyPrint: String = s"($hi:$lo)" } case class SumExpression(expressions: List[(Boolean, Expression)], decimal: Boolean) extends Expression { @@ -157,6 +165,7 @@ case class SumExpression(expressions: List[(Boolean, Expression)], decimal: Bool override def #-#(that: Expression): Expression = if (decimal) super.#-#(that) else SumExpression(expressions :+ (true -> that), decimal = false) + override def prettyPrint: String = '(' + expressions.map{case(neg, e) => (if (neg) "- " else "+ ") + e.prettyPrint}.mkString("", " ", ")").stripPrefix("+ ") } case class FunctionCallExpression(functionName: String, expressions: List[Expression]) extends Expression { @@ -176,6 +185,10 @@ case class FunctionCallExpression(functionName: String, expressions: List[Expres override def getPointies: Seq[String] = expressions.flatMap(_.getPointies) override def isPure: Boolean = false // TODO override def getAllIdentifiers: Set[String] = expressions.map(_.getAllIdentifiers).fold(Set[String]())(_ ++ _) + functionName + override def prettyPrint: String = + if (expressions.size != 2 || functionName.exists(Character.isAlphabetic(_))) + functionName + expressions.mkString("(", ", ", ")") + else s"(${expressions.head.prettyPrint} $functionName ${expressions(1).prettyPrint}" } case class HalfWordExpression(expression: Expression, hiByte: Boolean) extends Expression { @@ -189,6 +202,7 @@ case class HalfWordExpression(expression: Expression, hiByte: Boolean) extends E override def getPointies: Seq[String] = expression.getPointies override def isPure: Boolean = expression.isPure override def getAllIdentifiers: Set[String] = expression.getAllIdentifiers + override def prettyPrint: String = '(' + expression.prettyPrint + (if (hiByte) ").hi" else ").lo") } sealed class NiceFunctionProperty(override val toString: String) @@ -310,6 +324,7 @@ case class VariableExpression(name: String) extends LhsExpression { override def getPointies: Seq[String] = if (name.endsWith(".addr.lo")) Seq(name.stripSuffix(".addr.lo")) else Seq.empty override def isPure: Boolean = true override def getAllIdentifiers: Set[String] = Set(name) + override def prettyPrint: String = name } case class IndexedExpression(name: String, index: Expression) extends LhsExpression { @@ -338,6 +353,7 @@ case class IndexedExpression(name: String, index: Expression) extends LhsExpress override def getPointies: Seq[String] = Seq(name) override def isPure: Boolean = index.isPure override def getAllIdentifiers: Set[String] = index.getAllIdentifiers + name + override def prettyPrint: String = s"$name[${index.prettyPrint}]" } case class IndirectFieldExpression(root: Expression, firstIndices: Seq[Expression], fields: Seq[(Boolean, String, Seq[Expression])]) extends LhsExpression { @@ -371,6 +387,10 @@ case class IndirectFieldExpression(root: Expression, firstIndices: Seq[Expressio override def isPure: Boolean = root.isPure && firstIndices.forall(_.isPure) && fields.forall(_._3.forall(_.isPure)) override def getAllIdentifiers: Set[String] = root.getAllIdentifiers ++ firstIndices.flatMap(_.getAllIdentifiers) ++ fields.flatMap(_._3.flatMap(_.getAllIdentifiers)) + + override def prettyPrint: String = root.prettyPrint + + firstIndices.map(i => '[' + i.prettyPrint + ']').mkString("") + + fields.map{case (dot, f, ixs) => (if (dot) "." else "->") + f + ixs.map(i => '[' + i.prettyPrint + ']').mkString("")} } case class DerefDebuggingExpression(inner: Expression, preferredSize: Int) extends LhsExpression { @@ -391,6 +411,7 @@ case class DerefDebuggingExpression(inner: Expression, preferredSize: Int) exten override def isPure: Boolean = inner.isPure override def getAllIdentifiers: Set[String] = inner.getAllIdentifiers + override def prettyPrint: String = s"¥deref(${inner.prettyPrint})" } case class DerefExpression(inner: Expression, offset: Int, targetType: Type) extends LhsExpression { @@ -410,6 +431,7 @@ case class DerefExpression(inner: Expression, offset: Int, targetType: Type) ext override def isPure: Boolean = inner.isPure override def getAllIdentifiers: Set[String] = inner.getAllIdentifiers + override def prettyPrint: String = s"¥deref(${inner.prettyPrint})" } sealed trait Statement extends Node { @@ -718,6 +740,22 @@ case class ForStatement(variable: String, start: Expression, end: Expression, di override def loopVariable: String = variable } +case class MemsetStatement(start: Expression, size: Constant, value: Expression, direction: ForDirection.Value, original: Option[ForStatement]) extends CompoundStatement { + if (original.isEmpty && direction != ForDirection.ParallelUntil) { + throw new IllegalArgumentException + } + override def getAllExpressions: List[Expression] = List(start, value) ++ original.toList.flatMap(_.getAllExpressions) + + override def getChildStatements: Seq[Statement] = original.toList.flatMap(_.getChildStatements) + + override def flatMap(f: ExecutableStatement => Option[ExecutableStatement]): Option[ExecutableStatement] = + // shouldn't ever yield None, as this is possible only in case of control-flow changing statements: + Some(copy(original = original.flatMap(_.flatMap(f).asInstanceOf[Option[ForStatement]]))) + + + override def loopVariable: String = original.fold("_none")(_.loopVariable) +} + case class ForEachStatement(variable: String, values: Either[Expression, List[Expression]], body: List[ExecutableStatement]) extends CompoundStatement { override def getAllExpressions: List[Expression] = VariableExpression(variable) :: (values.fold[List[Expression]](_ => Nil, identity) ++ body.flatMap(_.getAllExpressions)) diff --git a/src/test/scala/millfork/test/MemBulkSuite.scala b/src/test/scala/millfork/test/MemBulkSuite.scala new file mode 100644 index 00000000..9e063058 --- /dev/null +++ b/src/test/scala/millfork/test/MemBulkSuite.scala @@ -0,0 +1,48 @@ +package millfork.test + +import millfork.Cpu +import millfork.test.emu.EmuCrossPlatformBenchmarkRun +import org.scalatest.{AppendedClues, FunSuite, Matchers} + +/** + * @author Karol Stasiak + */ +class MemBulkSuite extends FunSuite with Matchers with AppendedClues { + + test("Memcpy should work fine") { + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)( + """ + | + |array input @$c000 = [5,89,6,1,8,6,87,52,6,45,8,52,8,6,14,89] + |array output [input.length] @$c100 + |byte size @$cfff + |void main() { + | word i + | for i,0,paralleluntil,input.length { output[i] = input[i] } + | size = input.length + |} + |""".stripMargin) { m => + val size = m.readByte(0xcfff) + size should be >(0) + for (i <- 0 until size) { + m.readByte(0xc000 + i) should equal(m.readByte(0xc100 + i)) withClue s"[$i]" + } + } + } + + test("Correctly increment array elements") { + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)( + """ + |array output [$100] @$c000 + |void main() { + | word i + | for i,0,paralleluntil,$100 { output[i] = 0 } + | for i,0,paralleluntil,$100 { output[i] += i.lo } + |} + |""".stripMargin) { m => + for (i <- 0 until 0x100) { + m.readByte(0xc000 + i) should equal(i) withClue s"[$i]" + } + } + } +} diff --git a/src/test/scala/millfork/test/MemsetSuite.scala b/src/test/scala/millfork/test/MemsetSuite.scala index e35f0aca..3103af57 100644 --- a/src/test/scala/millfork/test/MemsetSuite.scala +++ b/src/test/scala/millfork/test/MemsetSuite.scala @@ -9,26 +9,45 @@ import org.scalatest.{AppendedClues, FunSuite, Matchers} */ class MemsetSuite extends FunSuite with Matchers with AppendedClues { - test("memset $1000") { - memsetCase(0x1000) + test("memset pointer $1000") { + memsetPointerCase(0x1000) } - test("memset $40") { - memsetCase(0x40) + test("memset pointer $40") { + memsetPointerCase(0x40) } - test("memset $80") { - memsetCase(0x80) + test("memset pointer $80") { + memsetPointerCase(0x80) } - test("memset $100") { - memsetCase(0x100) + test("memset pointer $100") { + memsetPointerCase(0x100) } - test("memset $200") { - memsetCase(0x200) + test("memset pointer $200") { + memsetPointerCase(0x200) } - test("memset $fff") { - memsetCase(0xfff) + test("memset pointer $fff") { + memsetPointerCase(0xfff) } - def memsetCase(size: Int): Unit = { + test("memset array $1000") { + memsetArrayCase(0x1000) + } + test("memset array $40") { + memsetArrayCase(0x40) + } + test("memset array $80") { + memsetArrayCase(0x80) + } + test("memset array $100") { + memsetArrayCase(0x100) + } + test("memset array $200") { + memsetArrayCase(0x200) + } + test("memset array $fff") { + memsetArrayCase(0xfff) + } + + def memsetPointerCase(size: Int): Unit = { EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Intel8080, Cpu.Z80, Cpu.Intel8086)( "const word SIZE = " + size + """ | array output [SIZE] @$c000 @@ -48,4 +67,47 @@ class MemsetSuite extends FunSuite with Matchers with AppendedClues { } } } + + def memsetArrayCase(size: Int): Unit = { + val t = if (size < 256) "byte" else "word" + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Intel8080, Cpu.Z80, Cpu.Intel8086)(s""" + | const $t SIZE = $size + | array output [SIZE] @$$c000 + | void main () { + | $t i + | for i,0,paralleluntil,SIZE { + | output[i] = 42 + | } + | } + """.stripMargin) { m => + for (addr <- 0 until 0x1000) { + if (addr < size) { + m.readByte(addr + 0xc000) should equal(42) withClue f"$$$addr%04x" + } else { + m.readByte(addr + 0xc000) should equal(0) withClue f"$$$addr%04x" + } + } + } + } + + test ("Tricky memset") { + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Intel8080, Cpu.Z80, Cpu.Intel8086)(""" + | const word SIZE = $800 + | array output [SIZE] @$c000 + | void main () { + | pointer p + | for p,output.addr,paralleluntil,output.addr+SIZE { + | p[0] = p.lo + | } + | } + """.stripMargin) { m => + for (addr <- 0 until 0x1000) { + if (addr < 0x800) { + m.readByte(addr + 0xc000) should equal(addr & 0xff) withClue f"$$$addr%04x" + } else { + m.readByte(addr + 0xc000) should equal(0) withClue f"$$$addr%04x" + } + } + } + } } \ No newline at end of file