mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-11 12:29:46 +00:00
Improve and optimize memset (see #47)
This commit is contained in:
parent
9f128d3c35
commit
f57ecc9800
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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 =>
|
||||
|
@ -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),
|
||||
|
@ -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) =>
|
||||
))) 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 =>
|
||||
))) 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)
|
||||
|
@ -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 <code>for i,a,until,b { p[i] = q[i] }</code>
|
||||
*/
|
||||
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 <code>a</code> is an arbitrary expression independent of <code>i</code>
|
||||
*/
|
||||
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 <code>for i,a,until,b { target[i] = z }</code>,
|
||||
* where <code>z</code> is an expression depending on <code>source[i]</code>
|
||||
*/
|
||||
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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
21
src/main/scala/millfork/env/Constant.scala
vendored
21
src/main/scala/millfork/env/Constant.scala
vendored
@ -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 _ => "?"
|
||||
}
|
||||
}
|
||||
|
42
src/main/scala/millfork/env/Environment.scala
vendored
42
src/main/scala/millfork/env/Environment.scala
vendored
@ -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 {
|
||||
|
5
src/main/scala/millfork/env/Thing.scala
vendored
5
src/main/scala/millfork/env/Thing.scala
vendored
@ -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 {
|
||||
|
@ -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))
|
||||
|
||||
|
48
src/test/scala/millfork/test/MemBulkSuite.scala
Normal file
48
src/test/scala/millfork/test/MemBulkSuite.scala
Normal file
@ -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]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user