From 6d499f3623e29ff1d38646d275520299a31e6fac Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Wed, 10 Jul 2019 16:51:12 +0200 Subject: [PATCH] Arrays with elements larger than one byte --- CHANGELOG.md | 2 + docs/lang/operators.md | 9 + docs/lang/syntax.md | 1 - .../compiler/AbstractExpressionCompiler.scala | 21 +- .../AbstractStatementPreprocessor.scala | 128 +++++++++++- .../millfork/compiler/mos/BuiltIns.scala | 8 +- .../compiler/mos/MosExpressionCompiler.scala | 187 +++++++++++++----- .../compiler/z80/Z80ExpressionCompiler.scala | 45 +++-- .../compiler/z80/Z80StatementCompiler.scala | 13 +- .../z80/Z80StatementPreprocessor.scala | 4 +- .../millfork/compiler/z80/ZBuiltIns.scala | 2 +- src/main/scala/millfork/env/Constant.scala | 2 + src/main/scala/millfork/env/Environment.scala | 18 +- src/main/scala/millfork/env/Pointy.scala | 3 +- src/main/scala/millfork/env/Thing.scala | 14 +- src/main/scala/millfork/node/CallGraph.scala | 2 +- src/main/scala/millfork/node/Node.scala | 14 +- .../millfork/node/opt/UnusedFunctions.scala | 2 +- .../node/opt/UnusedGlobalVariables.scala | 2 +- .../node/opt/UnusedLocalVariables.scala | 2 +- .../millfork/output/AbstractAssembler.scala | 52 ++--- src/main/scala/millfork/parser/MfParser.scala | 13 +- src/test/scala/millfork/test/ArraySuite.scala | 109 ++++++++-- .../scala/millfork/test/PointerSuite.scala | 73 ++++++- .../millfork/test/emu/ShouldNotCompile.scala | 35 +++- 25 files changed, 603 insertions(+), 158 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a49fb1..fd2e0a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current version +* Added arrays of elements of size greater than byte. + * Improved passing of register parameters to assembly functions. * Enabled declaring multiple variables in one line. diff --git a/docs/lang/operators.md b/docs/lang/operators.md index 0c6a86bf..b915ebad 100644 --- a/docs/lang/operators.md +++ b/docs/lang/operators.md @@ -238,6 +238,15 @@ an access to the element of the array `a` at the location assigned to the key `i * otherwise: a compile error +Note that you cannot access a whole array element if it's bigger than 2 bytes, but you can access its fields or take its pointer: + + array(int32) a[6] + + a[2] // not ok + a[2].b0 // ok + a[2].loword // ok + a[2].pointer // ok + ## Built-in functions * `not`: negation of a boolean expression diff --git a/docs/lang/syntax.md b/docs/lang/syntax.md index 69a67130..5307d624 100644 --- a/docs/lang/syntax.md +++ b/docs/lang/syntax.md @@ -129,7 +129,6 @@ Since 0.3.4, only const arrays can be allocated to ROM, non-const arrays are all and their contents are uninitialized before a call to `init_rw_memory`. See [the ROM vs RAM guide](../api/rom-vs-ram.md). * ``: type of the elements of the array. -It must be of size 1 byte. If omitted, the default is `byte`. * ``: either a constant number, which then defines the size of the array, diff --git a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala index 343a5cfb..0c6b3d5e 100644 --- a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala @@ -185,12 +185,12 @@ class AbstractExpressionCompiler[T <: AbstractCode] { def validateTypeCastAndGetSourceExpressionType(ctx: CompilationContext, typ: Type, params: List[Expression]): Type = { var failed = false - if (typ.name == "pointer") { - ctx.log.error("Cannot cast into pointer") + if (typ.name == "pointer" && typ.name !="pointer" && !typ.isInstanceOf[PointerType]) { + ctx.log.error("Cannot cast into pointer", params.headOption.flatMap(_.position)) failed = true } if (params.length != 1) { - ctx.log.error("Type casting should have exactly one argument") + ctx.log.error("Type casting should have exactly one argument", params.headOption.flatMap(_.position)) failed = true } val sourceType = getExpressionType(ctx, params.head) @@ -286,8 +286,19 @@ object AbstractExpressionCompiler { ok = false } } - for ((fieldName, indices) <- fieldPath) { - if (ok) { + for ((dot, fieldName, indices) <- fieldPath) { + if (dot && ok) { + fieldName match { + case "addr" => env.get[Type]("pointer") + case "pointer" => env.get[Type]("pointer." + currentType.name) + case "addr.hi" => b + case "addr.lo" => b + case "pointer.hi" => b + case "pointer.lo" => b + log.error(s"Unexpected subfield `$fieldName`", expr.position) + ok = false + } + } else if (ok) { currentType match { case PointerType(_, _, Some(targetType)) => val tuples = env.getSubvariables(targetType).filter(x => x._1 == "." + fieldName) diff --git a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala index 69b06a2b..9ea07e4f 100644 --- a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala +++ b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala @@ -128,12 +128,12 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte cv = search(target, cv) Assignment(optimizeExpr(target, cv).asInstanceOf[LhsExpression], optimizeExpr(arg, cv)).pos(pos) -> cv case Assignment(target:IndexedExpression, arg) if isWordPointy(target.name) => - if (isNonzero(target.index)) { - ctx.log.error("Pointers to word variables can be only indexed by 0") - } cv = search(arg, cv) cv = search(target, cv) - Assignment(DerefExpression(VariableExpression(target.name).pos(pos), 0, env.getPointy(target.name).elementType).pos(pos), optimizeExpr(arg, cv)).pos(pos) -> cv + Assignment(DerefExpression(SumExpression(List( + false -> FunctionCallExpression("pointer", List(VariableExpression(target.name).pos(pos))).pos(pos), + false -> FunctionCallExpression("<<", List(optimizeExpr(target.index, cv), LiteralExpression(1, 1))).pos(pos) + ), decimal = false), 0, env.getPointy(target.name).elementType).pos(pos), optimizeExpr(arg, cv)).pos(pos) -> cv case Assignment(target:IndexedExpression, arg) => cv = search(arg, cv) cv = search(target, cv) @@ -272,6 +272,9 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte case _ => } } + implicit class StringToFunctionNameOps(val functionName: String) { + def <|(exprs: Expression*): Expression = FunctionCallExpression(functionName, exprs.toList).pos(exprs.head.position) + } // generic warnings: expr match { case FunctionCallExpression("*" | "*=", params) => @@ -299,15 +302,55 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte env.eval(index) match { case Some(NumericConstant(0, _)) => //ok case _ => + // TODO: should we keep this? env.log.error(s"Type `$pt` can be only indexed with 0") } DerefExpression(result, 0, target) case x if x.isPointy => + val (targetType, arraySizeInBytes) = result match { + case VariableExpression(maybePointy) => + val pointy = env.getPointy(maybePointy) + pointy.elementType -> (pointy match { + case p:ConstantPointy => p.sizeInBytes + case _ => None + }) + case _ => env.get[Type](x.pointerTargetName) -> None + } + ctx.log.trace(s"$result is $x and targets $targetType") env.eval(index) match { - case Some(NumericConstant(n, _)) if n >= 0 && n <= 127 => - DerefExpression(result, n.toInt, b) + case Some(NumericConstant(n, _)) if n >= 0 && (targetType.size * n) <= 127 => + x match { + case _: PointerType => + DerefExpression(result, n.toInt, targetType) + case _ => + DerefExpression( + ("pointer." + targetType.name) <| result, + n.toInt, targetType) + } case _ => - DerefExpression(SumExpression(List(false -> result, false -> index), decimal = false), 0, b) + val scaledIndex = arraySizeInBytes match { + case Some(n) if n <= 256 => targetType.size match { + case 1 => "byte" <| index + case 2 => "<<" <| ("byte" <| index, LiteralExpression(1, 1)) + case 4 => "<<" <| ("byte" <| index, LiteralExpression(2, 1)) + case 8 => "<<" <| ("byte" <| index, LiteralExpression(3, 1)) + case _ => "*" <| ("byte" <| index, LiteralExpression(targetType.size, 1)) + } + case Some(n) if n <= 512 && targetType.size == 2 => + "nonet" <| ("<<" <| ("byte" <| index, LiteralExpression(1, 1))) + case _ => targetType.size match { + case 1 => "word" <| index + case 2 => "<<" <| ("word" <| index, LiteralExpression(1, 1)) + case 4 => "<<" <| ("word" <| index, LiteralExpression(2, 1)) + case 8 => "<<" <| ("word" <| index, LiteralExpression(3, 1)) + case _ => "*" <| ("word" <| index, LiteralExpression(targetType.size, 1)) + } + } + // TODO: re-cast pointer type + DerefExpression(("pointer." + targetType.name) <| SumExpression(List( + false -> result, + false -> optimizeExpr(scaledIndex, Map()) + ), decimal = false), 0, targetType) } case _ => ctx.log.error("Not a pointer type on the left-hand side of `[`", pos) @@ -319,9 +362,37 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte for (index <- firstIndices) { result = applyIndex(result, index) } - for ((fieldName, indices) <- fieldPath) { - if (ok) { - result = AbstractExpressionCompiler.getExpressionType(env, env.log, result) match { + for ((dot, fieldName, indices) <- fieldPath) { + if (dot && ok) { + val pointer = result match { + case DerefExpression(inner, 0, _) => + inner + case DerefExpression(inner, offset, targetType) => + ("pointer." + targetType.name) <| SumExpression(List( + false -> ("pointer" <| inner), + false -> LiteralExpression(offset, 2) + ), decimal = false) + case IndexedExpression(name, index) => + ctx.log.fatal("Oops!") + case _ => + ok = false + ctx.log.error(s"Not a left-hand-side expression", result.position) + result + } + fieldName match { + case "pointer" => result = pointer + case "pointer.hi" => result = "hi" <| pointer + case "pointer.lo" => result = "lo" <| pointer + case "addr" => result = "pointer" <| pointer + case "addr.hi" => result = "hi" <| pointer + case "addr.lo" => result = "lo" <| pointer + case _ => + ctx.log.error(s"Unexpected subfield `$fieldName`", result.position) + ok = false + } + } else if (ok) { + val currentResultType = AbstractExpressionCompiler.getExpressionType(env, env.log, result) + result = currentResultType match { case PointerType(_, _, Some(target)) => val subvariables = env.getSubvariables(target).filter(x => x._1 == "." + fieldName) if (subvariables.isEmpty) { @@ -333,6 +404,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte } case _ => ctx.log.error("Invalid pointer type on the left-hand side of `->`", result.position) + ctx.log.debug(currentResultType.toString) LiteralExpression(0, 1) } } @@ -379,6 +451,42 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte // don't collapse additions, let the later stages deal with it // expecially important when inside a nonet operation SumExpression(expressions.map{case (minus, arg) => minus -> optimizeExpr(arg, currentVarValues)}, decimal) + case IndexedExpression(name, index) => + val pointy = env.getPointy(name) + val targetType = pointy.elementType + targetType.size match { + case 1 => IndexedExpression(name, optimizeExpr(index, Map())).pos(pos) + case _ => + if (targetType.size != 2) { + ctx.log.error("Cannot access a large array element directly", expr.position) + } + val arraySizeInBytes = pointy match { + case p:ConstantPointy => p.sizeInBytes + case _ => None + } + val scaledIndex = arraySizeInBytes match { + case Some(n) if n <= 256 => targetType.size match { + case 1 => "byte" <| index + case 2 => "<<" <| ("byte" <| index, LiteralExpression(1, 1)) + case 4 => "<<" <| ("byte" <| index, LiteralExpression(2, 1)) + case 8 => "<<" <| ("byte" <| index, LiteralExpression(3, 1)) + case _ => "*" <| ("byte" <| index, LiteralExpression(targetType.size, 1)) + } + case Some(n) if n <= 512 && targetType.size == 2 => + "nonet" <| ("<<" <| ("byte" <| index, LiteralExpression(1, 1))) + case _ => targetType.size match { + case 1 => "word" <| index + case 2 => "<<" <| ("word" <| index, LiteralExpression(1, 1)) + case 4 => "<<" <| ("word" <| index, LiteralExpression(2, 1)) + case 8 => "<<" <| ("word" <| index, LiteralExpression(3, 1)) + case _ => "*" <| ("word" <| index, LiteralExpression(targetType.size, 1)) + } + } + DerefExpression(SumExpression(List( + false -> ("pointer" <| VariableExpression(name).pos(pos)), + false -> optimizeExpr(scaledIndex, Map()) + ), decimal = false), 0, pointy.elementType).pos(pos) + } case _ => expr // TODO } } diff --git a/src/main/scala/millfork/compiler/mos/BuiltIns.scala b/src/main/scala/millfork/compiler/mos/BuiltIns.scala index 2284a3c9..a386bba0 100644 --- a/src/main/scala/millfork/compiler/mos/BuiltIns.scala +++ b/src/main/scala/millfork/compiler/mos/BuiltIns.scala @@ -333,7 +333,7 @@ object BuiltIns { def compileInPlaceWordOrLongShiftOps(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, aslRatherThanLsr: Boolean): List[AssemblyLine] = { if (lhs.isInstanceOf[DerefExpression]) { - ctx.log.error("Too complex left-hand-side expression") + ctx.log.error("Too complex left-hand-side expression", lhs.position) return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, rhs) } val env = ctx.env @@ -945,7 +945,7 @@ object BuiltIns { def compileInPlaceWordMultiplication(ctx: CompilationContext, v: LhsExpression, addend: Expression): List[AssemblyLine] = { if (v.isInstanceOf[DerefExpression]) { - ctx.log.error("Too complex left-hand-side expression") + ctx.log.error("Too complex left-hand-side expression", v.position) return MosExpressionCompiler.compileToAX(ctx, v) ++ MosExpressionCompiler.compileToAX(ctx, addend) } val b = ctx.env.get[Type]("byte") @@ -1205,7 +1205,7 @@ object BuiltIns { return compileInPlaceWordOrLongAddition(ctx, lhs, addend, subtract, decimal = false) } if (lhs.isInstanceOf[DerefExpression]) { - ctx.log.error("Too complex left-hand-side expression") + ctx.log.error("Too complex left-hand-side expression", lhs.position) return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, addend) } val env = ctx.env @@ -1534,7 +1534,7 @@ object BuiltIns { def compileInPlaceWordOrLongBitOp(ctx: CompilationContext, lhs: LhsExpression, param: Expression, operation: Opcode.Value): List[AssemblyLine] = { if (lhs.isInstanceOf[DerefExpression]) { - ctx.log.error("Too complex left-hand-side expression") + ctx.log.error("Too complex left-hand-side expression", lhs.position) return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, param) } val env = ctx.env diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index 320a8b52..695a2a10 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -337,7 +337,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { ctx.log.error("Writing to a constant array", target.position) } val w = env.get[VariableType]("word") - wrapWordIndexingStorage(prepareWordIndexing(ctx, ConstantPointy(p.value + constIndex, None, if (constIndex.isProvablyZero) p.size else None, w, p.elementType, NoAlignment, p.readOnly), v)) + wrapWordIndexingStorage(prepareWordIndexing(ctx, ConstantPointy(p.value + constIndex, None, if (constIndex.isProvablyZero) p.sizeInBytes else None, if (constIndex.isProvablyZero) p.elementCount else None, w, p.elementType, NoAlignment, p.readOnly), v)) case (p: ConstantPointy, Some(v), 1, _) => if (p.readOnly) { ctx.log.error("Writing to a constant array", target.position) @@ -406,13 +406,13 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { Nil } case DerefExpression(inner, offset, targetType) => - val (prepare, reg) = getPhysicalPointerForDeref(ctx, inner) - val lo = preserveRegisterIfNeeded(ctx, MosRegister.A, prepare) ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine.indexedY(STA, reg)) + val (prepare, addr, am) = getPhysicalPointerForDeref(ctx, inner) + val lo = preserveRegisterIfNeeded(ctx, MosRegister.A, prepare) ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(STA, am, addr)) if (targetType.size == 1) { lo } else { lo ++ List(AssemblyLine.immediate(LDA, 0)) ++ - List.tabulate(targetType.size - 1)(i => List(AssemblyLine.implied(INY), AssemblyLine.indexedY(STA, reg))).flatten + List.tabulate(targetType.size - 1)(i => List(AssemblyLine.implied(INY), AssemblyLine(STA, am, addr))).flatten } } } @@ -437,14 +437,18 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { compile(ctx, expr, Some(p -> env.get[Variable]("__reg.loword")), BranchSpec.None) } - def getPhysicalPointerForDeref(ctx: CompilationContext, pointerExpression: Expression): (List[AssemblyLine], ThingInMemory) = { + def getPhysicalPointerForDeref(ctx: CompilationContext, pointerExpression: Expression): (List[AssemblyLine], Constant, AddrMode.Value) = { pointerExpression match { case VariableExpression(name) => val p = ctx.env.get[ThingInMemory](name) - if (p.zeropage) return Nil -> p + if (p.isInstanceOf[MfArray]) return (Nil, p.toAddress, AddrMode.AbsoluteY) + if (p.zeropage) return (Nil, p.toAddress, AddrMode.IndexedY) case _ => } - compileToZReg(ctx, pointerExpression) -> ctx.env.get[ThingInMemory]("__reg.loword") + ctx.env.eval(pointerExpression) match { + case Some(addr) => (Nil, addr, AddrMode.AbsoluteY) + case _ => (compileToZReg(ctx, pointerExpression), ctx.env.get[ThingInMemory]("__reg.loword").toAddress, AddrMode.IndexedY) + } } def compileStackOffset(ctx: CompilationContext, target: Variable, offset: Int, subbyte: Option[Int]): List[AssemblyLine] = { @@ -807,6 +811,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case IndexedExpression(arrayName, indexExpr) => val pointy = env.getPointy(arrayName) AbstractExpressionCompiler.checkIndexType(ctx, pointy, indexExpr) + if (pointy.elementType.size != 1) ctx.log.fatal("Whee!") // the statement preprocessor should have removed all of those // TODO: check val (variableIndex, constantIndex) = env.evalVariableAndConstantSubParts(indexExpr) val variableIndexSize = variableIndex.map(v => getExpressionType(ctx, v).size).getOrElse(0) @@ -870,7 +875,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { prepareWordIndexing(ctx, ConstantPointy( a.value + constantIndex, None, - if (constantIndex.isProvablyZero) a.size else None, + if (constantIndex.isProvablyZero) a.sizeInBytes else None, + if (constantIndex.isProvablyZero) a.elementCount else None, env.get[VariableType]("word"), a.elementType, NoAlignment, a.readOnly), v) ++ loadFromReg() case (a: VariablePointy, _, 2, _) => @@ -918,18 +924,26 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { } } case DerefExpression(inner, offset, targetType) => - val (prepare, reg) = getPhysicalPointerForDeref(ctx, inner) - targetType.size match { - case 1 => - prepare ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine.indexedY(LDA, reg)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position) - case 2 => + val (prepare, addr, am) = getPhysicalPointerForDeref(ctx, inner) + (targetType.size, am) match { + case (1, AbsoluteY) => + prepare ++ List(AssemblyLine.absolute(LDA, addr + offset)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position) + case (1, _) => + prepare ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(LDA, am, addr)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position) + case (2, AbsoluteY) => + prepare ++ + List( + AssemblyLine.absolute(LDA, addr + offset), + AssemblyLine.absolute(LDX, addr + offset + 1)) ++ + expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position) + case (2, _) => prepare ++ List( AssemblyLine.immediate(LDY, offset+1), - AssemblyLine.indexedY(LDA, reg), + AssemblyLine(LDA, am, addr), AssemblyLine.implied(TAX), AssemblyLine.implied(DEY), - AssemblyLine.indexedY(LDA, reg)) ++ + AssemblyLine(LDA, am, addr)) ++ expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position) case _ => ctx.log.error("Cannot read a large object indirectly") @@ -1287,6 +1301,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { l match { case v: VariableExpression => BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = false, decimal = false) + case _ => + ctx.log.error("Cannot modify large object accessed via such complex expression", l.position) + compile(ctx, r, None, BranchSpec.None) } } case "-=" => @@ -1303,6 +1320,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { l match { case v: VariableExpression => BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = true, decimal = false) + case _ => + ctx.log.error("Cannot modify large object accessed via such complex expression", l.position) + compile(ctx, r, None, BranchSpec.None) } } case "+'=" => @@ -1319,6 +1339,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { l match { case v: VariableExpression => BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = false, decimal = true) + case _ => + ctx.log.error("Cannot modify large object accessed via such complex expression", l.position) + compile(ctx, r, None, BranchSpec.None) } } case "-'=" => @@ -1335,6 +1358,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { l match { case v: VariableExpression => BuiltIns.compileInPlaceWordOrLongAddition(ctx, v, r, subtract = true, decimal = true) + case _ => + ctx.log.error("Cannot modify large object accessed via such complex expression", l.position) + compile(ctx, r, None, BranchSpec.None) } } case "<<=" => @@ -1389,6 +1415,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { BuiltIns.compileInPlaceByteMultiplication(ctx, l, r) case 2 => BuiltIns.compileInPlaceWordMultiplication(ctx, l, r) + case _ => ctx.log.fatal("Oops") } case "/=" | "%%=" => assertSizesForDivision(ctx, params, inPlace = true) @@ -1402,6 +1429,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { } else { compileAssignment(ctx, FunctionCallExpression("/", List(l, r)).pos(f.position), l) } + case _ => ctx.log.fatal("Oops") } case "/" | "%%" => assertSizesForDivision(ctx, params, inPlace = false) @@ -1838,69 +1866,122 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { ctx.log.error("Invalid left-hand-side use of `:`") Nil case DerefExpression(inner, offset, targetType) => - val (prepare, reg) = getPhysicalPointerForDeref(ctx, inner) + val (prepare, addr, am) = getPhysicalPointerForDeref(ctx, inner) env.eval(source) match { case Some(constant) => - targetType.size match { - case 1 => + (targetType.size, am) match { + case (1, AbsoluteY) => prepare ++ List( - AssemblyLine.immediate(LDY, offset), - AssemblyLine.immediate(LDA, constant), - AssemblyLine.indexedY(STA, reg)) - case 2 => + AssemblyLine.immediate(LDA, constant), + AssemblyLine.absolute(STA, addr + offset)) + case (1, _) => prepare ++ List( - AssemblyLine.immediate(LDY, offset), - AssemblyLine.immediate(LDA, constant.loByte), - AssemblyLine.indexedY(STA, reg), - AssemblyLine.implied(INY), - AssemblyLine.immediate(LDA, constant.hiByte), - AssemblyLine.indexedY(STA, reg)) + AssemblyLine.immediate(LDY, offset), + AssemblyLine.immediate(LDA, constant), + AssemblyLine(STA, am, addr)) + case (2, AbsoluteY) => + prepare ++ List( + AssemblyLine.immediate(LDA, constant.loByte), + AssemblyLine.absolute(STA, addr + offset), + AssemblyLine.immediate(LDA, constant.hiByte), + AssemblyLine.absolute(STA, addr + offset + 1)) + case (2, _) => + prepare ++ List( + AssemblyLine.immediate(LDY, offset), + AssemblyLine.immediate(LDA, constant.loByte), + AssemblyLine(STA, am, addr), + AssemblyLine.implied(INY), + AssemblyLine.immediate(LDA, constant.hiByte), + AssemblyLine(STA, am, addr)) + case _ => + ctx.log.error("Cannot assign to a large object indirectly", target.position) + Nil } case None => source match { case VariableExpression(vname) => val variable = env.get[Variable](vname) - targetType.size match { - case 1 => + (targetType.size, am) match { + case (1, AbsoluteY) => prepare ++ - AssemblyLine.variable(ctx, LDA, variable) ++ List( - AssemblyLine.immediate(LDY, offset), - AssemblyLine.indexedY(STA, reg)) - case 2 => + AssemblyLine.variable(ctx, LDA, variable) :+ + AssemblyLine.absolute(STA, addr + offset) + case (1, _) => prepare ++ AssemblyLine.variable(ctx, LDA, variable) ++ List( AssemblyLine.immediate(LDY, offset), - AssemblyLine.indexedY(STA, reg)) ++ + AssemblyLine(STA, am, addr)) + case (2, AbsoluteY) => + prepare ++ + AssemblyLine.variable(ctx, LDA, variable) ++ List( + AssemblyLine.absolute(STA, addr + offset)) ++ + AssemblyLine.variable(ctx, LDA, variable, 1) ++ List( + AssemblyLine.absolute(STA, addr + offset + 1)) + case (2, _) => + prepare ++ + AssemblyLine.variable(ctx, LDA, variable) ++ List( + AssemblyLine.immediate(LDY, offset), + AssemblyLine(STA, am, addr)) ++ AssemblyLine.variable(ctx, LDA, variable, 1) ++ List( AssemblyLine.implied(INY), - AssemblyLine.indexedY(STA, reg)) + AssemblyLine(STA, am, addr)) case _ => - ctx.log.error("Cannot assign to a large object indirectly") + ctx.log.error("Cannot assign to a large object indirectly", target.position) Nil } case _ => - targetType.size match { - case 1 => + (targetType.size, am) match { + case (1, _) => compile(ctx, source, Some(targetType, RegisterVariable(MosRegister.A, targetType)), BranchSpec.None) ++ compileByteStorage(ctx, MosRegister.A, target) - case 2 => + case (2, AbsoluteY) => val someTuple = Some(targetType, RegisterVariable(MosRegister.AX, targetType)) - // TODO: optimiza if prepare is empty - compile(ctx, source, someTuple, BranchSpec.None) ++ List( - AssemblyLine.implied(PHA), - AssemblyLine.implied(TXA), - AssemblyLine.implied(PHA)) ++ prepare ++ List( - AssemblyLine.immediate(LDY, offset+1), - AssemblyLine.implied(PLA), - AssemblyLine.indexedY(STA, reg), - AssemblyLine.implied(PLA), - AssemblyLine.implied(DEY), - AssemblyLine.indexedY(STA, reg)) + // TODO: optimize if prepare is empty + if (prepare.isEmpty) { + compile(ctx, source, someTuple, BranchSpec.None) ++ List( + AssemblyLine.absolute(STA, addr + offset), + AssemblyLine.absolute(STX, addr + offset + 1)) + } else { + compile(ctx, source, someTuple, BranchSpec.None) ++ List( + AssemblyLine.implied(PHA), + AssemblyLine.implied(TXA), + AssemblyLine.implied(PHA)) ++ prepare ++ List( + AssemblyLine.implied(PLA), + AssemblyLine.absolute(STA, addr + offset + 1), + AssemblyLine.implied(PLA), + AssemblyLine.absolute(STA, addr + offset)) + } + case (2, _) => + val someTuple = Some(targetType, RegisterVariable(MosRegister.AX, targetType)) + if (prepare.isEmpty) { + compile(ctx, source, someTuple, BranchSpec.None) ++ List( + AssemblyLine.immediate(LDY, offset), + AssemblyLine.indexedY(STA, addr), + AssemblyLine.implied(TXA), + AssemblyLine.implied(INY), + AssemblyLine.indexedY(STA, addr)) + } else { + compile(ctx, source, someTuple, BranchSpec.None) ++ List( + AssemblyLine.implied(PHA), + AssemblyLine.implied(TXA), + AssemblyLine.implied(PHA)) ++ prepare ++ List( + AssemblyLine.immediate(LDY, offset+1), + AssemblyLine.implied(PLA), + AssemblyLine.indexedY(STA, addr), + AssemblyLine.implied(PLA), + AssemblyLine.implied(DEY), + AssemblyLine.indexedY(STA, addr)) + } case _ => - ctx.log.error("Cannot assign to a large object indirectly") + ctx.log.error("Cannot assign to a large object indirectly", target.position) Nil } } } + case i: IndexedExpression => + if (AbstractExpressionCompiler.getExpressionType(ctx, target).size != 1) { + ctx.log.error("Cannot store a large object this way", target.position) + } + compile(ctx, source, Some(b, RegisterVariable(MosRegister.A, b)), NoBranching) ++ compileByteStorage(ctx, MosRegister.A, target) case _ => compile(ctx, source, Some(b, RegisterVariable(MosRegister.A, b)), NoBranching) ++ compileByteStorage(ctx, MosRegister.A, target) } @@ -1910,7 +1991,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { if (!ctx.options.flags(CompilationFlag.CheckIndexOutOfBounds)) return Nil val arrayLength:Int = pointy match { case _: VariablePointy => return Nil - case p: ConstantPointy => p.size match { + case p: ConstantPointy => p.sizeInBytes match { case None => return Nil case Some(s) => s } diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index 36a4a3a1..6a2573e1 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -1198,23 +1198,32 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { val env = ctx.env val pointy = env.getPointy(i.name) AbstractExpressionCompiler.checkIndexType(ctx, pointy, i.index) + val elementSize = pointy.elementType.size + val logElemSize = elementSize match { + case 1 => 0 + case 2 => 1 + case _ => + ctx.log.error("Cannot access a large object this way", i.position) + 0 + } pointy match { - case ConstantPointy(baseAddr, _, size, _, _, alignment, readOnly) => + case ConstantPointy(baseAddr, _, sizeInBytes, _, _, _, alignment, readOnly) => if (forWriting && readOnly) { ctx.log.error("Writing to a constant array", i.position) } env.evalVariableAndConstantSubParts(i.index) match { - case (None, offset) => List(ZLine.ldImm16(ZRegister.HL, (baseAddr + offset).quickSimplify)) + case (None, offset) => List(ZLine.ldImm16(ZRegister.HL, (baseAddr + offset * elementSize).quickSimplify)) case (Some(index), offset) => - val constantPart = (baseAddr + offset).quickSimplify - if (getExpressionType(ctx, i.index).size == 1 && size.exists(_ < 256) && alignment == WithinPageAlignment) { - compileToA(ctx, i.index) ++ List( + val constantPart = (baseAddr + offset * elementSize).quickSimplify + if (getExpressionType(ctx, i.index).size == 1 && sizeInBytes.exists(_ < 256) && alignment == WithinPageAlignment) { + compileToA(ctx, i.index) ++ List.fill(logElemSize)(ZLine.register(ADD, ZRegister.A)) ++ List( ZLine.imm8(ADD, constantPart.loByte), ZLine.ld8(ZRegister.L, ZRegister.A), ZLine.ldImm8(ZRegister.H, constantPart.hiByte)) } else { List(ZLine.ldImm16(ZRegister.BC, constantPart)) ++ stashBCIfChanged(ctx, compileToHL(ctx, index)) ++ + List.fill(logElemSize)(ZLine.registers(ADD_16, ZRegister.HL, ZRegister.HL)) ++ List(ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC)) } } @@ -1234,9 +1243,8 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case _ => if (ctx.options.flag(CompilationFlag.EmitIntel8080Opcodes)) { compileToBC(ctx, i.index) ++ - List( - ZLine.ldAbs16(ZRegister.HL, varAddr), - ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC)) + List(ZLine.ldAbs16(ZRegister.HL, varAddr)) ++ + List.fill(elementSize)(ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC)) } else { // TODO: is this reasonable? compileToBC(ctx, i.index) ++ @@ -1244,14 +1252,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { ZLine.ldAbs8(ZRegister.A, varAddr), ZLine.ld8(ZRegister.L, ZRegister.A), ZLine.ldAbs8(ZRegister.A, varAddr + 1), - ZLine.ld8(ZRegister.H, ZRegister.A), - ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC)) + ZLine.ld8(ZRegister.H, ZRegister.A)) ++ + List.fill(elementSize)(ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC)) } } case _: StackVariablePointy => compileToHL(ctx, VariableExpression(i.name).pos(i.position)) ++ stashHLIfChanged(ctx, compileToBC(ctx, i.index)) ++ - List(ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC)) + List.fill(elementSize)(ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC)) } } @@ -1489,6 +1497,17 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { } } + def storeConstantWord(ctx: CompilationContext, target: LhsExpression, source: Constant, signedSource: Boolean): List[ZLine] = { + target match { + case e: DerefExpression => + compileDerefPointer(ctx, e) ++ List( + ZLine.ldImm8(ZRegister.MEM_HL, source.loByte), + ZLine.register(INC_16, ZRegister.HL), + ZLine.ldImm8(ZRegister.MEM_HL, source.hiByte)) + case _ => ZLine.ldImm16(ZRegister.HL, source) :: storeHL(ctx, target, signedSource) + } + } + def storeHL(ctx: CompilationContext, target: LhsExpression, signedSource: Boolean): List[ZLine] = { val env = ctx.env target match { @@ -1535,6 +1554,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { env.evalVariableAndConstantSubParts(indexExpr) match { case (None, offset) => ZLine.ld8(ZRegister.A, ZRegister.L) :: storeA(ctx, (p.value + offset).quickSimplify, 1, signedSource) } + case _ => ctx.log.fatal("Whee!") // the statement preprocessor should have removed all of those } case SeparateBytesExpression(hi: LhsExpression, lo: LhsExpression) => Z80ExpressionCompiler.stashHLIfChanged(ctx, ZLine.ld8(ZRegister.A, ZRegister.L) :: storeA(ctx, lo, signedSource)) ++ @@ -1846,6 +1866,9 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { Nil } } + case _ => + ctx.log.error("Cannot modify large object accessed via such complex expression", lhs.position) + List.fill(size)(Nil) } } diff --git a/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala index f5d9f025..85af8e55 100644 --- a/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala @@ -16,6 +16,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] { def compile(ctx: CompilationContext, statement: ExecutableStatement): (List[ZLine], List[ZLine])= { + ctx.log.trace(statement.toString) val options = ctx.options val env = ctx.env val ret = Z80Compiler.restoreRegistersAndReturn(ctx) @@ -84,13 +85,23 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] { }) -> Nil case Assignment(destination, source) => val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source) + val targetType = AbstractExpressionCompiler.getExpressionType(ctx, destination) + AbstractExpressionCompiler.checkAssignmentType(ctx, source, targetType) (sourceType.size match { case 0 => ctx.log.error("Cannot assign a void expression", statement.position) Z80ExpressionCompiler.compile(ctx, source, ZExpressionTarget.NOTHING, BranchSpec.None) ++ Z80ExpressionCompiler.compile(ctx, destination, ZExpressionTarget.NOTHING, BranchSpec.None) case 1 => Z80ExpressionCompiler.compileToA(ctx, source) ++ Z80ExpressionCompiler.storeA(ctx, destination, sourceType.isSigned) - case 2 => Z80ExpressionCompiler.compileToHL(ctx, source) ++ Z80ExpressionCompiler.storeHL(ctx, destination, sourceType.isSigned) + case 2 => + ctx.env.eval(source) match { + case Some(constantWord) => + Z80ExpressionCompiler.storeConstantWord(ctx, destination, constantWord, sourceType.isSigned) + case _ => + val load = Z80ExpressionCompiler.compileToHL(ctx, source) + val store = Z80ExpressionCompiler.storeHL(ctx, destination, sourceType.isSigned) + load ++ store + } case s => Z80ExpressionCompiler.storeLarge(ctx, destination, source) }) -> Nil case s: IfStatement => diff --git a/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala b/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala index 48cf932a..ef495154 100644 --- a/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala +++ b/src/main/scala/millfork/compiler/z80/Z80StatementPreprocessor.scala @@ -29,7 +29,7 @@ class Z80StatementPreprocessor(ctx: CompilationContext, statements: List[Executa case f: DerefDebuggingExpression => Nil case IndexedExpression(a, VariableExpression(v)) => if (v == variable) { ctx.env.maybeGet[Thing](a + ".array") match { - case Some(_: MfArray) => Seq(a) + case Some(array: MfArray) if array.elementType.size == 1 => Seq(a) case _ => Nil } } else Nil @@ -130,7 +130,7 @@ class Z80StatementPreprocessor(ctx: CompilationContext, statements: List[Executa val array = ctx.env.get[MfArray](name + ".array") Assignment( VariableExpression(newVariables(name, f.variable)), - FunctionCallExpression("pointer." + array.elementType.name, List( + FunctionCallExpression("pointer", List( SumExpression(List(false -> VariableExpression(name + ".addr"), false -> optStart), decimal = false) ))) }).toList :+ ForStatement(f.variable, optStart, optimizeExpr(f.end, Map()), newDirection, optimizeStmts(newBody, Map())._1), diff --git a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala index a4073adf..d3f680b8 100644 --- a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala +++ b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala @@ -468,7 +468,7 @@ object ZBuiltIns { def performLongInPlace(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, opcodeFirst: ZOpcode.Value, opcodeLater: ZOpcode.Value, size: Int, decimal: Boolean = false): List[ZLine] = { if (lhs.isInstanceOf[DerefExpression]) { - ctx.log.error("Too complex left-hand-side expression") + ctx.log.error("Too complex left-hand-side expression", lhs.position) return Z80ExpressionCompiler.compileToHL(ctx, lhs) ++ Z80ExpressionCompiler.compileToHL(ctx, rhs) } if (size == 2 && !decimal) { diff --git a/src/main/scala/millfork/env/Constant.scala b/src/main/scala/millfork/env/Constant.scala index 46fc2747..0447f8d5 100644 --- a/src/main/scala/millfork/env/Constant.scala +++ b/src/main/scala/millfork/env/Constant.scala @@ -42,6 +42,8 @@ sealed trait Constant { def +(that: Constant): Constant = CompoundConstant(MathOperator.Plus, this, that) + def *(scale: Int): Constant = CompoundConstant(MathOperator.Times, this, NumericConstant(scale, Constant.minimumSize(scale) min 2)).quickSimplify + def -(that: Constant): Constant = CompoundConstant(MathOperator.Minus, this, that) def +(that: Long): Constant = if (that == 0) this else this + NumericConstant(that, minimumSize(that)) diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index db7fd17c..90306866 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -354,13 +354,13 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa InitializedMemoryVariable UninitializedMemoryVariable getArrayOrPointer(name) match { - case th@InitializedArray(_, _, cs, _, i, e, ro, _) => ConstantPointy(th.toAddress, Some(name), Some(cs.length), i, e, th.alignment, readOnly = ro) - case th@UninitializedArray(_, size, _, i, e, ro, _) => ConstantPointy(th.toAddress, Some(name), Some(size), i, e, th.alignment, readOnly = ro) - case th@RelativeArray(_, _, size, _, i, e, ro) => ConstantPointy(th.toAddress, Some(name), Some(size), i, e, NoAlignment, readOnly = ro) + case th@InitializedArray(_, _, cs, _, i, e, ro, _) => ConstantPointy(th.toAddress, Some(name), Some(e.size * cs.length), Some(cs.length), i, e, th.alignment, readOnly = ro) + case th@UninitializedArray(_, elementCount, _, i, e, ro, _) => ConstantPointy(th.toAddress, Some(name), Some(elementCount * e.size), Some(elementCount / e.size), i, e, th.alignment, readOnly = ro) + case th@RelativeArray(_, _, elementCount, _, i, e, ro) => ConstantPointy(th.toAddress, Some(name), Some(elementCount * e.size), Some(elementCount / e.size), i, e, NoAlignment, readOnly = ro) case ConstantThing(_, value, typ) if typ.size <= 2 && typ.isPointy => val e = get[VariableType](typ.pointerTargetName) val w = get[VariableType]("word") - ConstantPointy(value, None, None, w, e, NoAlignment, readOnly = false) + ConstantPointy(value, None, None, None, w, e, NoAlignment, readOnly = false) case th:VariableInMemory if th.typ.isPointy=> val e = get[VariableType](th.typ.pointerTargetName) val w = get[VariableType]("word") @@ -373,7 +373,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa log.error(s"$name is not a valid pointer or array") val b = get[VariableType]("byte") val w = get[VariableType]("word") - ConstantPointy(Constant.Zero, None, None, w, b, NoAlignment, readOnly = false) + ConstantPointy(Constant.Zero, None, None, None, w, b, NoAlignment, readOnly = false) } } @@ -599,7 +599,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa } case IndexedExpression(arrName, index) => getPointy(arrName) match { - case ConstantPointy(MemoryAddressConstant(arr:InitializedArray), _, _, _, _, _, _) if arr.readOnly && arr.elementType.size == 1 => + case ConstantPointy(MemoryAddressConstant(arr:InitializedArray), _, _, _, _, _, _, _) if arr.readOnly && arr.elementType.size == 1 => eval(index).flatMap { case NumericConstant(constIndex, _) => if (constIndex >= 0 && constIndex < arr.sizeInBytes) { @@ -1303,8 +1303,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa val w = get[VariableType]("word") val p = get[Type]("pointer") val e = get[VariableType](stmt.elementType) - if (e.size != 1) { - log.error(s"Array elements should be of size 1, `${e.name}` is of size ${e.size}", stmt.position) + if (e.size < 1 && e.size > 127) { + log.error(s"Array elements should be of size between 1 and 127, `${e.name}` is of size ${e.size}", stmt.position) } stmt.elements match { case None => @@ -1841,7 +1841,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa case IndirectFieldExpression(inner, firstIndices, fields) => nameCheck(inner) firstIndices.foreach(nameCheck) - fields.foreach(f => f._2.foreach(nameCheck)) + fields.foreach(f => f._3.foreach(nameCheck)) case SeparateBytesExpression(h, l) => nameCheck(h) nameCheck(l) diff --git a/src/main/scala/millfork/env/Pointy.scala b/src/main/scala/millfork/env/Pointy.scala index 700f6644..1f95aad9 100644 --- a/src/main/scala/millfork/env/Pointy.scala +++ b/src/main/scala/millfork/env/Pointy.scala @@ -21,7 +21,8 @@ case class VariablePointy(addr: Constant, indexType: VariableType, elementType: case class ConstantPointy(value: Constant, name: Option[String], - size: Option[Int], + sizeInBytes: Option[Int], + elementCount: Option[Int], indexType: VariableType, elementType: VariableType, alignment: MemoryAlignment, diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 63f8e858..01007e17 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -266,12 +266,12 @@ trait MfArray extends ThingInMemory with IndexableThing { def indexType: VariableType def elementType: VariableType override def isVolatile: Boolean = false - /* TODO: what if larger elements? */ def sizeInBytes: Int + def elementCount: Int def readOnly: Boolean } -case class UninitializedArray(name: String, /* TODO: what if larger elements? */ sizeInBytes: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType, override val readOnly: Boolean, override val alignment: MemoryAlignment) extends MfArray with UninitializedMemory { +case class UninitializedArray(name: String, elementCount: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType, override val readOnly: Boolean, override val alignment: MemoryAlignment) extends MfArray with UninitializedMemory { override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this) override def alloc: VariableAllocationMethod.Value = VariableAllocationMethod.Static @@ -281,9 +281,11 @@ case class UninitializedArray(name: String, /* TODO: what if larger elements? */ override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse("default") override def zeropage: Boolean = false + + override def sizeInBytes: Int = elementCount * elementType.size } -case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType, override val readOnly: Boolean) extends MfArray { +case class RelativeArray(name: String, address: Constant, elementCount: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType, override val readOnly: Boolean) extends MfArray { override def toAddress: Constant = address override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false) @@ -291,6 +293,8 @@ case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, decl override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse("default") override def zeropage: Boolean = false + + override def sizeInBytes: Int = elementCount * elementType.size } 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 { @@ -303,7 +307,9 @@ case class InitializedArray(name: String, address: Option[Constant], contents: S override def zeropage: Boolean = false - override def sizeInBytes: Int = contents.size + override def elementCount: Int = contents.size + + override def sizeInBytes: Int = contents.size * elementType.size } case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean, declaredBank: Option[String], override val isVolatile: Boolean) extends VariableInMemory { diff --git a/src/main/scala/millfork/node/CallGraph.scala b/src/main/scala/millfork/node/CallGraph.scala index 9d465cd2..f34c1c0e 100644 --- a/src/main/scala/millfork/node/CallGraph.scala +++ b/src/main/scala/millfork/node/CallGraph.scala @@ -70,7 +70,7 @@ abstract class CallGraph(program: Program, log: Logger) { case IndirectFieldExpression(root, firstIndices, fields) => add(currentFunction, callingFunctions, root) firstIndices.foreach(i => add(currentFunction, callingFunctions, i)) - fields.foreach(f => f._2.foreach(i => add(currentFunction, callingFunctions, i))) + fields.foreach(f => f._3.foreach(i => add(currentFunction, callingFunctions, i))) case _ => () } } diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index 5ef4f141..d38ae0d4 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -237,32 +237,32 @@ case class IndexedExpression(name: String, index: Expression) extends LhsExpress override def getAllIdentifiers: Set[String] = index.getAllIdentifiers + name } -case class IndirectFieldExpression(root: Expression, firstIndices: Seq[Expression], fields: Seq[(String, Seq[Expression])]) extends LhsExpression { +case class IndirectFieldExpression(root: Expression, firstIndices: Seq[Expression], fields: Seq[(Boolean, String, Seq[Expression])]) extends LhsExpression { override def replaceVariable(variable: String, actualParam: Expression): Expression = IndirectFieldExpression( root.replaceVariable(variable, actualParam), firstIndices.map(_.replaceVariable(variable, actualParam)), - fields.map{case (f, i) => f -> i.map(_.replaceVariable(variable, actualParam))}) + fields.map{case (dot, f, i) => (dot, f, i.map(_.replaceVariable(variable, actualParam)))}) override def replaceIndexedExpression(predicate: IndexedExpression => Boolean, replacement: IndexedExpression => Expression): Expression = IndirectFieldExpression( root.replaceIndexedExpression(predicate, replacement), firstIndices.map(_.replaceIndexedExpression(predicate, replacement)), - fields.map{case (f, i) => f -> i.map(_.replaceIndexedExpression(predicate, replacement))}) + fields.map{case (dot, f, i) => (dot, f, i.map(_.replaceIndexedExpression(predicate, replacement)))}) override def containsVariable(variable: String): Boolean = root.containsVariable(variable) || firstIndices.exists(_.containsVariable(variable)) || - fields.exists(_._2.exists(_.containsVariable(variable))) + fields.exists(_._3.exists(_.containsVariable(variable))) override def getPointies: Seq[String] = (root match { case VariableExpression(v) => List(v) case _ => root.getPointies - }) ++ firstIndices.flatMap(_.getPointies) ++ fields.flatMap(_._2.flatMap(_.getPointies)) + }) ++ firstIndices.flatMap(_.getPointies) ++ fields.flatMap(_._3.flatMap(_.getPointies)) - override def isPure: Boolean = root.isPure && firstIndices.forall(_.isPure) && fields.forall(_._2.forall(_.isPure)) + 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(_._2.flatMap(_.getAllIdentifiers)) + override def getAllIdentifiers: Set[String] = root.getAllIdentifiers ++ firstIndices.flatMap(_.getAllIdentifiers) ++ fields.flatMap(_._3.flatMap(_.getAllIdentifiers)) } case class DerefDebuggingExpression(inner: Expression, preferredSize: Int) extends LhsExpression { diff --git a/src/main/scala/millfork/node/opt/UnusedFunctions.scala b/src/main/scala/millfork/node/opt/UnusedFunctions.scala index 8e1273f3..1c61f52c 100644 --- a/src/main/scala/millfork/node/opt/UnusedFunctions.scala +++ b/src/main/scala/millfork/node/opt/UnusedFunctions.scala @@ -120,7 +120,7 @@ object UnusedFunctions extends NodeOptimization { case IndexedExpression(arr, index) => arr :: getAllCalledFunctions(List(index)) case SeparateBytesExpression(h, l) => getAllCalledFunctions(List(h, l)) case DerefDebuggingExpression(inner, _) => getAllCalledFunctions(List(inner)) - case IndirectFieldExpression(root, firstIndices, fieldPath) => getAllCalledFunctions(root :: firstIndices ++: fieldPath.flatMap(_._2).toList) + case IndirectFieldExpression(root, firstIndices, fieldPath) => getAllCalledFunctions(root :: firstIndices ++: fieldPath.flatMap(_._3).toList) case _ => Nil } diff --git a/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala b/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala index 5100866d..200bed9e 100644 --- a/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala +++ b/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala @@ -64,7 +64,7 @@ object UnusedGlobalVariables extends NodeOptimization { case FunctionCallExpression(name, xs) => name :: getAllReadVariables(xs) case IndexedExpression(arr, index) => arr :: getAllReadVariables(List(index)) case SeparateBytesExpression(h, l) => getAllReadVariables(List(h, l)) - case IndirectFieldExpression(root, firstIndices, fields) => getAllReadVariables(List(root) ++ firstIndices ++ fields.flatMap(_._2)) + case IndirectFieldExpression(root, firstIndices, fields) => getAllReadVariables(List(root) ++ firstIndices ++ fields.flatMap(_._3)) case _ => Nil } diff --git a/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala b/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala index bfa17333..85f8657c 100644 --- a/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala +++ b/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala @@ -44,7 +44,7 @@ object UnusedLocalVariables extends NodeOptimization { case IndexedExpression(arr, index) => arr :: getAllReadVariables(List(index)) case DerefExpression(inner, _, _) => getAllReadVariables(List(inner)) case DerefDebuggingExpression(inner, _) => getAllReadVariables(List(inner)) - case IndirectFieldExpression(inner, firstIndices, fields) => getAllReadVariables(List(inner) ++ firstIndices ++ fields.flatMap(_._2)) + case IndirectFieldExpression(inner, firstIndices, fields) => getAllReadVariables(List(inner) ++ firstIndices ++ fields.flatMap(_._3)) case SeparateBytesExpression(h, l) => getAllReadVariables(List(h, l)) case _ => Nil } diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index c7fe9d71..d5a6aae2 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -267,7 +267,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program }) env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach { - case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _, _, _, readOnly, _) => + case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _, _, elementType, readOnly, _) => val bank = thing.bank(options) if (!readOnly && options.platform.ramInitialValuesBank.isDefined) { log.error(s"Preinitialized writable array $name cannot be put at a fixed address") @@ -278,22 +278,25 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program assembly.append(name + ":") for (item <- items) { env.eval(item) match { - case Some(c) => writeByte(bank, index, c) + case Some(c) => + for(i <- 0 until elementType.size) { + writeByte(bank, index, c.subbyte(i)) + bank0.occupied(index) = true + bank0.initialized(index) = true + bank0.writeable(index) = true + bank0.readable(index) = true + index += 1 + } case None => log.error(s"Non-constant contents of array `$name`", item.position) } - bank0.occupied(index) = true - bank0.initialized(index) = true - bank0.writeable(index) = true - bank0.readable(index) = true - index += 1 } - items.grouped(16).foreach { group => - assembly.append(" " + bytePseudoopcode + " " + group.map(expr => env.eval(expr) match { - case Some(c) => c.quickSimplify.toString - case None => "" - }).mkString(", ")) + items.flatMap(expr => env.eval(expr) match { + case Some(c) => List.tabulate(elementType.size)(i => c.subbyte(i).quickSimplify.toString) + case None => List.fill(elementType.size)("") + }).grouped(16).foreach { group => + assembly.append(" " + bytePseudoopcode + " " + group.mkString(", ")) } - initializedVariablesSize += items.length + initializedVariablesSize += thing.sizeInBytes case thing@InitializedArray(name, Some(_), items, _, _, _, _, _) => ??? case f: NormalFunction if f.address.isDefined => val bank = f.bank(options) @@ -390,32 +393,35 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program } } env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach { - case thing@InitializedArray(name, None, items, _, _, _, readOnly, alignment) if readOnly == readOnlyPass => + case thing@InitializedArray(name, None, items, _, _, elementType, readOnly, alignment) if readOnly == readOnlyPass => val bank = thing.bank(options) if (options.platform.ramInitialValuesBank.isDefined && !readOnly && bank != "default") { log.error(s"Preinitialized writable array `$name` should be defined in the `default` bank") } val bank0 = mem.banks(bank) - var index = codeAllocators(bank).allocateBytes(bank0, options, items.size, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment) + var index = codeAllocators(bank).allocateBytes(bank0, options, thing.sizeInBytes, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment) labelMap(name) = bank0.index -> index if (!readOnlyPass) { rwDataStart = rwDataStart.min(index) - rwDataEnd = rwDataEnd.min(index + items.size) + rwDataEnd = rwDataEnd.min(index + thing.sizeInBytes) } assembly.append("* = $" + index.toHexString) assembly.append(name + ":") for (item <- items) { env.eval(item) match { - case Some(c) => writeByte(bank, index, c) + case Some(c) => + for (i <- 0 until elementType.size) { + writeByte(bank, index, c.subbyte(i)) + index += 1 + } case None => log.error(s"Non-constant contents of array `$name`", item.position) } - index += 1 } - items.grouped(16).foreach { group => - assembly.append(" " + bytePseudoopcode + " " + group.map(expr => env.eval(expr) match { - case Some(c) => c.quickSimplify.toString - case None => "" - }).mkString(", ")) + items.flatMap(expr => env.eval(expr) match { + case Some(c) => List.tabulate(elementType.size)(i => c.subbyte(i).quickSimplify.toString) + case None => List.fill(elementType.size)("") + }).grouped(16).foreach { group => + assembly.append(" " + bytePseudoopcode + " " + group.mkString(", ")) } initializedVariablesSize += items.length justAfterCode += bank -> index diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index f12e2daa..19b30c6e 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -326,11 +326,20 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri def mfExpressionWrapper[E <: Expression](inner: P[E]): P[E] = for { expr <- inner firstIndices <- index.rep - fieldPath <- (HWS ~ "->" ~/ AWS ~/ identifier ~/ index.rep).rep + fieldPath <- (HWS ~ (("->".! ~/ AWS) | ".".!) ~/ AWS ~/ identifier ~/ index.rep).rep } yield (expr, firstIndices, fieldPath) match { case (_, Seq(), Seq()) => expr case (VariableExpression(vname), Seq(i), Seq()) => IndexedExpression(vname, i).pos(expr.position).asInstanceOf[E] - case _ => IndirectFieldExpression(expr, firstIndices, fieldPath).pos(expr.position).asInstanceOf[E] + case _ => + val fixedFieldPath = fieldPath.flatMap { e => + e match { + case (".", "pointer", _) => Seq(e) + case (".", f, _) if f.startsWith("pointer.") => Seq(e) + case (".", f, i) => Seq((".", "pointer", Nil), ("->", f, i)) + case _ => Seq(e) + } + } + IndirectFieldExpression(expr, firstIndices, fixedFieldPath.map {case (a,b,c) => (a == ".", b, c)}).pos(expr.position).asInstanceOf[E] } // def mfLhsExpression: P[LhsExpression] = for { diff --git a/src/test/scala/millfork/test/ArraySuite.scala b/src/test/scala/millfork/test/ArraySuite.scala index be63e715..44804677 100644 --- a/src/test/scala/millfork/test/ArraySuite.scala +++ b/src/test/scala/millfork/test/ArraySuite.scala @@ -60,21 +60,27 @@ class ArraySuite extends FunSuite with Matchers { } test("Array assignment with offset 1") { - val m = new EmuRun(Cpu.StrictMos, Nil, DangerousOptimizations.All ++ OptimizationPresets.Good)( - """ - | array output [8] @$c000 - | void main () { - | byte i - | i = 0 - | while i != 6 { - | output[i + 2] = i + 1 - | output[i] = output[i] - | i += 1 - | } - | } - """.stripMargin) + try { + val m = new EmuRun(Cpu.StrictMos, Nil, DangerousOptimizations.All ++ OptimizationPresets.Good)( + """ + | array output [8] @$c000 + | void main () { + | byte i + | i = 0 + | while i != 6 { + | output[i + 2] = i + 1 + | output[i] = output[i] + | i += 1 + | } + | } + """.stripMargin) m.readByte(0xc002) should equal(1) m.readByte(0xc007) should equal(6) + } catch { + case th: Throwable => + th.printStackTrace(System.err) + throw th + } } test("Array assignment through a pointer") { @@ -375,4 +381,81 @@ class ArraySuite extends FunSuite with Matchers { | } """.stripMargin).readByte(0xc000) should equal(1) } + + test("Arrays of words") { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Intel8080, Cpu.Z80)( + """ + | array(word) words[10] @$c000 + | void main () { + | words[2] = $702 + | words[3] = $201 + | memory_barrier() + | words[1] = words[3] + | words[4] = words[3] + words[2] + | words[5] = $101 + | words[5] = words[5] << 1 + | words[5] = words[5] + $1001 + | } + """.stripMargin){ m => + m.readWord(0xc004) should equal(0x702) + m.readWord(0xc006) should equal(0x201) + m.readWord(0xc002) should equal(0x201) + m.readWord(0xc008) should equal(0x903) + m.readWord(0xc00a) should equal(0x1203) + } + } + + test("Initialized arrays of words") { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Intel8080, Cpu.Z80)( + """ + | struct coord { byte x, byte y } + | + | array(word) a = [1,2,3] + | array(coord) c = [coord(1,2),coord(3,4)] + | + | word output @$c000 + | coord output2 @$c002 + | + | void main () { + | output = a[2] + | output2 = c[1] + | } + | + """.stripMargin){ m => + m.readWord(0xc000) should equal(3) + m.readByte(0xc002) should equal(3) + m.readByte(0xc003) should equal(4) + } + } + + test("Invalid array things that will become valid in the future") { + ShouldNotCompile( + """ + | array(int32) a[7] @$c000 + | void main () { + | a[0] = 2 + | } + """.stripMargin) + ShouldNotCompile( + """ + | array(int32) a[7] @$c000 + | void main () { + | a[0] += 2 + | } + """.stripMargin) + ShouldNotCompile( + """ + | array(word) a[7] @$c000 + | void main () { + | a[0] += 2 + | } + """.stripMargin) + ShouldNotCompile( + """ + | array(int32) a[7] @$c000 + | int32 main () { + | return a[4] + | } + """.stripMargin) + } } diff --git a/src/test/scala/millfork/test/PointerSuite.scala b/src/test/scala/millfork/test/PointerSuite.scala index 9ef24bd4..f294b414 100644 --- a/src/test/scala/millfork/test/PointerSuite.scala +++ b/src/test/scala/millfork/test/PointerSuite.scala @@ -1,7 +1,7 @@ package millfork.test import millfork.Cpu -import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun} +import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun, ShouldNotCompile} import org.scalatest.{AppendedClues, FunSuite, Matchers} /** @@ -231,4 +231,75 @@ class PointerSuite extends FunSuite with Matchers with AppendedClues { """.stripMargin) { m => } } + + test("Pointers and arrays to large elements") { + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)( + """ + | struct P { + | word i + | byte c + | byte d + | } + | + | array(P) a [8] + | + | noinline byte f(byte i) { + | return a[i].c + | } + | + | void main() { + | f(6) + | } + """.stripMargin) { m => + } + } + + test("Page crossing with arrays of large elements") { + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)( + """ + | import zp_reg + | struct P { + | word i + | byte c + | byte d + | } + | + | array(P) a [80] @$c080 + | array(P) b [500] @$c280 + | + | noinline void fill(word i) { + | if i < 80 { a[lo(i)].i = i } + | b[i].i = i + | } + | + | void main() { + | word i + | for i,0,until,500 { fill(i) } + | } + """.stripMargin) { m => + for (i <- 0 until 80) { + m.readWord(0xc080 + 4*i) should equal(i) withClue s"a[$i]" + } + for (i <- 0 until 500) { + m.readWord(0xc280 + 4*i) should equal(i) withClue s"b[$i]" + } + } + } + + test("Word pointers") { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080) ( + """ + |pointer.word p + |word output @$c000 + |void main () { + | word tmp + | p = tmp.pointer + | tmp = $203 + | output = p[0] + |} + """.stripMargin + ){ m => + m.readWord(0xc000) should equal(0x203) + } + } } diff --git a/src/test/scala/millfork/test/emu/ShouldNotCompile.scala b/src/test/scala/millfork/test/emu/ShouldNotCompile.scala index 37dffb9f..1089c62a 100644 --- a/src/test/scala/millfork/test/emu/ShouldNotCompile.scala +++ b/src/test/scala/millfork/test/emu/ShouldNotCompile.scala @@ -10,6 +10,9 @@ import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, N import millfork.node.StandardCallGraph import millfork.parser.{MosParser, PreprocessingResult, Preprocessor} import millfork._ +import millfork.compiler.m6809.M6809Compiler +import millfork.compiler.z80.Z80Compiler +import millfork.output.{M6809Assembler, MosAssembler, Z80Assembler} import org.scalatest.Matchers import scala.collection.JavaConverters._ @@ -52,7 +55,12 @@ object ShouldNotCompile extends Matchers { // print unoptimized asm env.allPreallocatables.foreach { case f: NormalFunction => - val unoptimized = MosCompiler.compile(CompilationContext(f.environment, f, 0, options, Set())) + val unoptimized = cpuFamily match { + case CpuFamily.M6502 => MosCompiler.compile(CompilationContext(f.environment, f, 0, options, Set())) + case CpuFamily.I80 => Z80Compiler.compile(CompilationContext(f.environment, f, 0, options, Set())) + case CpuFamily.M6809 => M6809Compiler.compile(CompilationContext(f.environment, f, 0, options, Set())) + case _ => Nil + } unoptimizedSize += unoptimized.map(_.sizeInBytes).sum case d: InitializedArray => unoptimizedSize += d.contents.length @@ -61,12 +69,27 @@ object ShouldNotCompile extends Matchers { } if (!log.hasErrors) { - val familyName = cpuFamily match { - case CpuFamily.M6502 => "6502" - case CpuFamily.I80 => "Z80" - case _ => "unknown CPU" + val env2 = new Environment(None, "", cpuFamily, options) + env2.collectDeclarations(program, options) + cpuFamily match { + case CpuFamily.M6502 => + val assembler = new MosAssembler(program, env2, platform) + val output = assembler.assemble(callGraph, Nil, options) + output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) + fail("Failed: Compilation succeeded for 6502") + case CpuFamily.I80 => + val assembler = new Z80Assembler(program, env2, platform) + val output = assembler.assemble(callGraph, Nil, options) + output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) + fail("Failed: Compilation succeeded for Z80") + case CpuFamily.M6809 => + val assembler = new M6809Assembler(program, env2, platform) + val output = assembler.assemble(callGraph, Nil, options) + output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) + fail("Failed: Compilation succeeded for 6809") + case _ => + fail("Failed: Compilation succeeded for unknown CPU") } - fail("Failed: Compilation succeeded for " + familyName) } log.clearErrors()