1
0
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:
Karol Stasiak 2020-03-15 01:06:09 +01:00
parent 9f128d3c35
commit f57ecc9800
14 changed files with 426 additions and 49 deletions

View File

@ -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
}
}
}

View File

@ -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))

View File

@ -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 =>

View File

@ -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),

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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 _ => "?"
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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))

View 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]"
}
}
}
}

View File

@ -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"
}
}
}
}
}