1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-16 18:29:31 +00:00

Arrays with elements larger than one byte

This commit is contained in:
Karol Stasiak 2019-07-10 16:51:12 +02:00
parent 65338555ad
commit 6d499f3623
25 changed files with 603 additions and 158 deletions

View File

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

View File

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

View File

@ -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).
* `<element type>`: type of the elements of the array.
It must be of size 1 byte.
If omitted, the default is `byte`.
* `<size>`: either a constant number, which then defines the size of the array,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 _ => ()
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 => "<? unknown constant ?>"
}).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)("<? unknown constant ?>")
}).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 => "<? unknown constant ?>"
}).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)("<? unknown constant ?>")
}).grouped(16).foreach { group =>
assembly.append(" " + bytePseudoopcode + " " + group.mkString(", "))
}
initializedVariablesSize += items.length
justAfterCode += bank -> index

View File

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

View File

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

View File

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

View File

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