1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-02-06 01:30:13 +00:00

Add manual type alignment. 6502: Optimize multiplications by constants.

This commit is contained in:
Karol Stasiak 2020-07-18 01:14:43 +02:00
parent 31a8504195
commit a2c49a1f89
17 changed files with 233 additions and 64 deletions

View File

@ -224,7 +224,7 @@ as there are no checks on values when converting bytes to enumeration values and
Struct is a compound type containing multiple fields of various types:
struct <name> { <field definitions (type and name), separated by commas or newlines>}
struct <name> [align (alignment)] { <field definitions (type and name), separated by commas or newlines>}
A struct is represented in memory as a contiguous area of variables laid out one after another.
@ -254,9 +254,27 @@ You can create constant expressions of struct types using so-called struct const
All arguments to the constructor must be constant.
Structures declared with an alignment are allocated at appropriate memory addresses.
The alignment has to be a power of two.
If the structs are in an array, they are padded with unused bytes.
If the struct is smaller that its alignment, then arrays of it are faster
struct a align(4) { byte x,byte y, byte z }
struct b { byte x,byte y, byte z }
array(a) as [4] @ $C000
array(b) bs [4] @ $C800
a[1].addr - a[0].addr // equals 4
b[1].addr - b[0].addr // equals 3
sizeof(a) // equals 16
sizeof(b) // equals 12
return a[i].x // requires XXXX cycles on 6502
return b[i].x // requires XXXX cycles on 6502
## Unions
union <name> { <field definitions (type and name), separated by commas or newlines>}
union <name> [align (alignment)] { <field definitions (type and name), separated by commas or newlines>}
Unions are pretty similar to structs, with the difference that all fields of the union
start at the same point in memory and therefore overlap each other.

View File

@ -0,0 +1,15 @@
package millfork
/**
* @author Karol Stasiak
*/
object MathUtils {
@scala.annotation.tailrec
def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
def lcm(a: Int, b: Int): Int =
(a.toLong & b.toLong / gcd(a, b)).toInt
}

View File

@ -64,7 +64,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
}
def isWordPointy(name: String): Boolean = {
env.getPointy(name).elementType.size == 2
env.getPointy(name).elementType.alignedSize == 2
}
def optimizeStmt(stmt: ExecutableStatement, currentVarValues: VV): (ExecutableStatement, VV) = {
@ -409,32 +409,39 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
}
ctx.log.trace(s"$result is $x and targets $targetType")
env.eval(index) match {
case Some(NumericConstant(n, _)) if n >= 0 && (targetType.size * n) <= 127 =>
case Some(NumericConstant(n, _)) if n >= 0 && (targetType.alignedSize * n) <= 127 =>
x match {
case _: PointerType =>
DerefExpression(result, targetType.size * n.toInt, targetType)
DerefExpression(result, targetType.alignedSize * n.toInt, targetType)
case _ =>
DerefExpression(
("pointer." + targetType.name) <| result,
targetType.size * n.toInt, targetType)
targetType.alignedSize * n.toInt, targetType)
}
case _ =>
val shifts = Integer.numberOfTrailingZeros(targetType.alignedSize)
val shrunkElementSize = targetType.alignedSize >> shifts
val shrunkArraySize = arraySizeInBytes.fold(9999)(_.>>(shifts))
val scaledIndex = arraySizeInBytes match {
case Some(n) if n <= 256 => targetType.size match {
case Some(n) if n <= 256 => targetType.alignedSize 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 _ => "*" <| ("byte" <| index, LiteralExpression(targetType.alignedSize, 1))
}
case Some(n) if n <= 512 && targetType.size == 2 =>
case Some(n) if n <= 512 && targetType.alignedSize == 2 =>
"nonet" <| ("<<" <| ("byte" <| index, LiteralExpression(1, 1)))
case _ => targetType.size match {
case Some(n) if n <= 512 && targetType.alignedSize == 2 =>
"nonet" <| ("<<" <| ("byte" <| index, LiteralExpression(1, 1)))
case Some(_) if shrunkArraySize <= 256 =>
"<<" <| ("word" <| ("*" <| ("byte" <| index, LiteralExpression(shrunkElementSize, 1))), LiteralExpression(shifts, 1))
case _ => targetType.alignedSize 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))
case _ => "*" <| ("word" <| index, LiteralExpression(targetType.alignedSize, 1))
}
}
// TODO: re-cast pointer type
@ -618,13 +625,13 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
case IndexedExpression(name, index) =>
val pointy = env.getPointy(name)
val targetType = pointy.elementType
targetType.size match {
targetType.alignedSize match {
case 1 => IndexedExpression(name, optimizeExpr(index, Map())).pos(pos)
case _ =>
val constantOffset: Option[Long] = env.eval(index) match {
case Some(z) if z.isProvablyZero => Some(0L)
case Some(NumericConstant(n, _)) =>
if (targetType.size * (n+1) <= 256) Some(targetType.size * n) else None
if (targetType.alignedSize * (n+1) <= 256) Some(targetType.alignedSize * n) else None
case _ => None
}
constantOffset match {
@ -643,22 +650,27 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
case p: ConstantPointy => p.sizeInBytes
case _ => None
}
val shifts = Integer.numberOfTrailingZeros(targetType.alignedSize)
val shrunkElementSize = targetType.alignedSize >> shifts
val shrunkArraySize = arraySizeInBytes.fold(9999)(_.>>(shifts))
val scaledIndex = arraySizeInBytes match {
case Some(n) if n <= 256 => targetType.size match {
case Some(n) if n <= 256 => targetType.alignedSize 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 _ => "*" <| ("byte" <| index, LiteralExpression(targetType.alignedSize, 1))
}
case Some(n) if n <= 512 && targetType.size == 2 =>
case Some(n) if n <= 512 && targetType.alignedSize == 2 =>
"nonet" <| ("<<" <| ("byte" <| index, LiteralExpression(1, 1)))
case _ => targetType.size match {
case Some(_) if shrunkArraySize <= 256 =>
"<<" <| ("word" <| ("*" <| ("byte" <| index, LiteralExpression(shrunkElementSize, 1))), LiteralExpression(shifts, 1))
case _ => targetType.alignedSize 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))
case _ => "*" <| ("word" <| index, LiteralExpression(targetType.alignedSize, 1))
}
}
DerefExpression(

View File

@ -142,7 +142,7 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
}
case v:VariablePointy =>
val (prepareIndex, offset): (List[MLine], Constant) = ctx.env.eval(index) match {
case Some(ix) => List(MLine.absolute(LDX, v.addr)) -> (ix * v.elementType.size).quickSimplify
case Some(ix) => List(MLine.absolute(LDX, v.addr)) -> (ix * v.elementType.alignedSize).quickSimplify
case _ =>
v.indexType.size match {
case 1 | 2 =>
@ -167,7 +167,7 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
}
case v:StackVariablePointy =>
ctx.env.eval(index) match {
case Some(ix) => List(MLine.variablestack(ctx, LDX, v.offset), MLine.indexedX(LDB, ix * v.elementType.size))
case Some(ix) => List(MLine.variablestack(ctx, LDX, v.offset), MLine.indexedX(LDB, ix * v.elementType.alignedSize))
}
}
case e@SumExpression(expressions, decimal) =>
@ -739,7 +739,7 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
ctx.env.eval(index) match {
case Some(ix) =>
if (ix.isProvablyZero) List(MLine(store, Absolute(true), v.addr))
else List(MLine.absolute(LDX, v.addr), MLine.indexedX(store, ix * v.elementType.size))
else List(MLine.absolute(LDX, v.addr), MLine.indexedX(store, ix * v.elementType.alignedSize))
case _ =>
v.indexType.size match {
case 1 | 2 =>
@ -752,7 +752,7 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
}
case v: StackVariablePointy =>
ctx.env.eval(index) match {
case Some(ix) => List(MLine.variablestack(ctx, LDX, v.offset), MLine.indexedX(store, ix * v.elementType.size))
case Some(ix) => List(MLine.variablestack(ctx, LDX, v.offset), MLine.indexedX(store, ix * v.elementType.alignedSize))
}
}
}
@ -827,14 +827,14 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
compileToX(ctx, inner) :+ MLine.indexedX(MOpcode.LEAX, Constant(offset))
case IndexedExpression(aname, index) =>
ctx.env.getPointy(aname) match {
case p: VariablePointy => compileToD(ctx, index #*# p.elementType.size) ++ List(MLine.absolute(ADDD, p.addr), MLine.tfr(M6809Register.D, M6809Register.X))
case p: VariablePointy => compileToD(ctx, index #*# p.elementType.alignedSize) ++ List(MLine.absolute(ADDD, p.addr), MLine.tfr(M6809Register.D, M6809Register.X))
case p: ConstantPointy =>
if (p.sizeInBytes.exists(_ < 255)) {
compileToB(ctx, index #*# p.elementType.size) ++ List(MLine.immediate(LDX, p.value), MLine.inherent(ABX))
compileToB(ctx, index #*# p.elementType.alignedSize) ++ List(MLine.immediate(LDX, p.value), MLine.inherent(ABX))
} else {
compileToX(ctx, index #*# p.elementType.size) :+ MLine.indexedX(LEAX, p.value)
compileToX(ctx, index #*# p.elementType.alignedSize) :+ MLine.indexedX(LEAX, p.value)
}
case p:StackVariablePointy => compileToD(ctx, index #*# p.elementType.size) ++ List(MLine.variablestack(ctx, ADDD, p.offset), MLine.tfr(M6809Register.D, M6809Register.X))
case p:StackVariablePointy => compileToD(ctx, index #*# p.elementType.alignedSize) ++ List(MLine.variablestack(ctx, ADDD, p.offset), MLine.tfr(M6809Register.D, M6809Register.X))
}
case _ => ???
}

View File

@ -1309,7 +1309,6 @@ object BuiltIns {
val adc = addingCode.last
val indexing = addingCode.init
result ++= indexing
result += AssemblyLine.immediate(LDA, 0)
val mult = c & 0xff
var mask = 128
var empty = true
@ -1318,8 +1317,12 @@ object BuiltIns {
result += AssemblyLine.implied(ASL)
}
if ((mult & mask) != 0) {
if (empty) {
result += adc.copy(opcode = LDA)
} else {
result += AssemblyLine.implied(CLC)
result += adc
}
empty = false
}

View File

@ -19,7 +19,7 @@ object MosBulkMemoryOperations {
return MosStatementCompiler.compileForStatement(ctx, f)._1
}
val pointy = ctx.env.getPointy(target.name)
if (pointy.elementType.size != 1) {
if (pointy.elementType.alignedSize != 1) {
return MosStatementCompiler.compileForStatement(ctx, f)._1
}
val w = ctx.env.get[Type]("word")

View File

@ -967,7 +967,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
if (pointy.elementType.alignedSize != 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)

View File

@ -435,7 +435,7 @@ object Z80BulkMemoryOperations {
loadA: ZOpcode.Value => List[ZLine],
z80Bulk: Boolean => Option[ZOpcode.Value]): List[ZLine] = {
val pointy = ctx.env.getPointy(target.name)
if (pointy.elementType.size > 1) return Z80StatementCompiler.compileForStatement(ctx, f)._1
if (pointy.elementType.alignedSize > 1) return Z80StatementCompiler.compileForStatement(ctx, f)._1
val targetOffset = removeVariableOnce(ctx, f.variable, target.index).getOrElse(return compileForStatement(ctx, f)._1)
if (!targetOffset.isPure) return compileForStatement(ctx, f)._1
val indexVariableSize = ctx.env.get[Variable](f.variable).typ.size

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(array: MfArray) if array.elementType.size == 1 => Seq(a)
case Some(array: MfArray) if array.elementType.alignedSize == 1 => Seq(a)
case _ => Nil
}
} else Nil

View File

@ -384,9 +384,9 @@ 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(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 th@InitializedArray(_, _, cs, _, i, e, ro, _) => ConstantPointy(th.toAddress, Some(name), Some(e.alignedSize * 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.alignedSize), 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.alignedSize), 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")
@ -535,8 +535,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
BranchingOpcodeMapping(Opcode.BPL, IfFlagClear(ZFlag.S), MOpcode.BPL),
BranchingOpcodeMapping(Opcode.BMI, IfFlagSet(ZFlag.S), MOpcode.BMI)),
None)
val byte_and_pointer$ = StructType("byte_and_pointer$", List(FieldDesc("byte", "zp", None), FieldDesc("pointer", "branch", None)))
val hudson_transfer$ = StructType("hudson_transfer$", List(FieldDesc("word", "a", None), FieldDesc("word", "b", None), FieldDesc("word", "c", None)))
val byte_and_pointer$ = StructType("byte_and_pointer$", List(FieldDesc("byte", "zp", None), FieldDesc("pointer", "branch", None)), NoAlignment)
val hudson_transfer$ = StructType("hudson_transfer$", List(FieldDesc("word", "a", None), FieldDesc("word", "b", None), FieldDesc("word", "c", None)), NoAlignment)
addThing(byte_and_pointer$, None)
addThing(hudson_transfer$, None)
Environment.constOnlyBuiltinFunction.foreach(n => addThing(ConstOnlyCallable(n), None))
@ -642,8 +642,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
hintTypo(name)
1
case Some(thing) => thing match {
case t: Type => t.size
case v: Variable => v.typ.size
case t: Type => t.alignedSize
case v: Variable => v.typ.alignedSize
case a: MfArray => a.sizeInBytes
case ConstantThing(_, MemoryAddressConstant(a: MfArray), _) => a.sizeInBytes
case x =>
@ -652,7 +652,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
case _ =>
AbstractExpressionCompiler.getExpressionType(this, log, expr).size
AbstractExpressionCompiler.getExpressionType(this, log, expr).alignedSize
}
NumericConstant(size, Constant.minimumSize(size))
}
@ -675,7 +675,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.alignedSize == 1 =>
evalImpl(index, vv).flatMap {
case NumericConstant(constIndex, _) =>
if (constIndex >= 0 && constIndex < arr.sizeInBytes) {
@ -1062,7 +1062,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
log.error(s"Invalid field name: `${f.fieldName}`", stmt.position)
}
}
addThing(StructType(stmt.name, stmt.fields), stmt.position)
addThing(StructType(stmt.name, stmt.fields, stmt.alignment.getOrElse(NoAlignment)), stmt.position)
}
def registerUnion(stmt: UnionDefinitionStatement): Unit = {
@ -1071,7 +1071,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
log.error(s"Invalid field name: `${f.fieldName}`", stmt.position)
}
}
addThing(UnionType(stmt.name, stmt.fields), stmt.position)
addThing(UnionType(stmt.name, stmt.fields, stmt.alignment.getOrElse(NoAlignment)), stmt.position)
}
def getTypeSize(t: VariableType, path: Set[String]): Int = {
@ -1765,7 +1765,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
val length = contents.length
if (length > 0xffff || length < 0) log.error(s"Array `${stmt.name}` has invalid length", stmt.position)
val alignment = stmt.alignment.getOrElse(defaultArrayAlignment(options, length))
val alignment = stmt.alignment.getOrElse(defaultArrayAlignment(options, length)) & e.alignment
val address = stmt.address.map(a => eval(a).getOrElse(errorConstant(s"Array `${stmt.name}` has non-constant address", stmt.position)))
for (element <- contents) {
AbstractExpressionCompiler.checkAssignmentTypeLoosely(this, element, e)
@ -1825,7 +1825,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val b = get[Type]("byte")
val w = get[Type]("word")
val typ = get[VariableType](stmt.typ)
val alignment = stmt.alignment.getOrElse(defaultVariableAlignment(options, typ.size))
val alignment = stmt.alignment.getOrElse(defaultVariableAlignment(options, typ.size)) & typ.alignment
if (stmt.constant) {
if (stmt.stack) log.error(s"`$name` is a constant and cannot be on stack", position)
if (stmt.register) log.error(s"`$name` is a constant and cannot be in a register", position)
@ -2161,8 +2161,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def fixStructSizes(): Unit = {
val allStructTypes: Iterable[VariableType] = things.values.flatMap {
case s@StructType(name, _) => Some(s)
case s@UnionType(name, _) => Some(s)
case s@StructType(name, _, _) => Some(s)
case s@UnionType(name, _, _) => Some(s)
case _ => None
}
var iterations = allStructTypes.size
@ -2180,7 +2180,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def fixStructFields(): Unit = {
// TODO: handle arrays?
things.values.foreach {
case st@StructType(_, fields) =>
case st@StructType(_, fields, _) =>
st.mutableFieldsWithTypes = fields.map {
case FieldDesc(tn, name, arraySize) => ResolvedFieldDesc(get[VariableType](tn), name, arraySize.map { x =>
eval(x) match {
@ -2191,7 +2191,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
})
}
case ut@UnionType(_, fields) =>
case ut@UnionType(_, fields, _) =>
ut.mutableFieldsWithTypes = fields.map {
case FieldDesc(tn, name, arraySize) => ResolvedFieldDesc(get[VariableType](tn), name, arraySize.map { x =>
eval(x) match {

View File

@ -24,6 +24,10 @@ sealed trait Type extends CallableThing {
def size: Int
def alignedSize: Int = alignment.roundSizeUp(size)
def alignment: MemoryAlignment
def isSigned: Boolean
def isBoollike: Boolean = false
@ -55,6 +59,8 @@ case object VoidType extends Type {
def isSigned = false
override def name = "void"
override def alignment: MemoryAlignment = NoAlignment
}
sealed trait PlainType extends VariableType {
@ -73,12 +79,16 @@ case class BasicPlainType(name: String, size: Int) extends PlainType {
def isSigned = false
override def isSubtypeOf(other: Type): Boolean = this == other
override def alignment: MemoryAlignment = NoAlignment
}
case class DerivedPlainType(name: String, parent: PlainType, isSigned: Boolean, override val isPointy: Boolean) extends PlainType {
def size: Int = parent.size
override def isSubtypeOf(other: Type): Boolean = parent == other || parent.isSubtypeOf(other)
override def alignment: MemoryAlignment = parent.alignment
}
case class PointerType(name: String, targetName: String, var target: Option[Type]) extends VariableType {
@ -89,6 +99,8 @@ case class PointerType(name: String, targetName: String, var target: Option[Type
override def isPointy: Boolean = true
override def pointerTargetName: String = targetName
override def alignment: MemoryAlignment = NoAlignment
}
case class FunctionPointerType(name: String, paramTypeName:String, returnTypeName: String, var paramType: Option[Type], var returnType: Option[Type]) extends VariableType {
@ -97,6 +109,8 @@ case class FunctionPointerType(name: String, paramTypeName:String, returnTypeNam
override def isSigned: Boolean = false
override def isPointy: Boolean = false
override def alignment: MemoryAlignment = NoAlignment
}
case object NullType extends VariableType {
@ -111,24 +125,28 @@ case object NullType extends VariableType {
override def isSubtypeOf(other: Type): Boolean = this == other || (other.isPointy || other.isInstanceOf[FunctionPointerType]) && other.size == 2
override def isAssignableTo(targetType: Type): Boolean = this == targetType || (targetType.isPointy || targetType.isInstanceOf[FunctionPointerType]) && targetType.size == 2
override def alignment: MemoryAlignment = NoAlignment
}
case class EnumType(name: String, count: Option[Int]) extends VariableType {
override def size: Int = 1
override def isSigned: Boolean = false
override def alignment: MemoryAlignment = NoAlignment
}
sealed trait CompoundVariableType extends VariableType
case class StructType(name: String, fields: List[FieldDesc]) extends CompoundVariableType {
case class StructType(name: String, fields: List[FieldDesc], override val alignment: MemoryAlignment) extends CompoundVariableType {
override def size: Int = mutableSize
var mutableSize: Int = -1
var mutableFieldsWithTypes: List[ResolvedFieldDesc] = Nil
override def isSigned: Boolean = false
}
case class UnionType(name: String, fields: List[FieldDesc]) extends CompoundVariableType {
case class UnionType(name: String, fields: List[FieldDesc], override val alignment: MemoryAlignment) extends CompoundVariableType {
override def size: Int = mutableSize
var mutableSize: Int = -1
var mutableFieldsWithTypes: List[ResolvedFieldDesc] = Nil
@ -151,6 +169,8 @@ case object FatBooleanType extends VariableType {
override def isAssignableTo(targetType: Type): Boolean = this == targetType
override def isExplicitlyCastableTo(targetType: Type): Boolean = targetType.isArithmetic || isAssignableTo(targetType)
override def alignment: MemoryAlignment = NoAlignment
}
sealed trait BooleanType extends Type {
@ -163,6 +183,8 @@ sealed trait BooleanType extends Type {
override def isAssignableTo(targetType: Type): Boolean = isCompatible(targetType) || targetType == FatBooleanType
override def isExplicitlyCastableTo(targetType: Type): Boolean = targetType.isArithmetic || isAssignableTo(targetType)
override def alignment: MemoryAlignment = NoAlignment
}
case class ConstantBooleanType(name: String, value: Boolean) extends BooleanType
@ -252,7 +274,7 @@ case class Placeholder(name: String, typ: Type) extends Variable {
}
sealed trait UninitializedMemory extends ThingInMemory {
def sizeInBytes: Int
def sizeInBytes: Int
def alloc: VariableAllocationMethod.Value
@ -284,7 +306,7 @@ case class UninitializedMemoryVariable(
declaredBank: Option[String],
override val alignment: MemoryAlignment,
override val isVolatile: Boolean) extends MemoryVariable with UninitializedMemory {
override def sizeInBytes: Int = typ.size
override def sizeInBytes: Int = typ.alignedSize
override def zeropage: Boolean = alloc == VariableAllocationMethod.Zeropage
@ -328,7 +350,7 @@ case class UninitializedArray(name: String, elementCount: Int, declaredBank: Opt
override def zeropage: Boolean = false
override def sizeInBytes: Int = elementCount * elementType.size
override def sizeInBytes: Int = elementCount * elementType.alignedSize
}
case class RelativeArray(name: String, address: Constant, elementCount: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType, override val readOnly: Boolean) extends MfArray {
@ -340,7 +362,7 @@ case class RelativeArray(name: String, address: Constant, elementCount: Int, dec
override def zeropage: Boolean = false
override def sizeInBytes: Int = elementCount * elementType.size
override def sizeInBytes: Int = elementCount * elementType.alignedSize
override def rootName: String = address.rootThingName
}
@ -357,7 +379,7 @@ case class InitializedArray(name: String, address: Option[Constant], contents: S
override def elementCount: Int = contents.size
override def sizeInBytes: Int = contents.size * elementType.size
override def sizeInBytes: Int = contents.size * elementType.alignedSize
}
case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean, declaredBank: Option[String], override val isVolatile: Boolean) extends VariableInMemory {

View File

@ -565,11 +565,11 @@ case class EnumDefinitionStatement(name: String, variants: List[(String, Option[
override def getAllExpressions: List[Expression] = variants.flatMap(_._2)
}
case class StructDefinitionStatement(name: String, fields: List[FieldDesc]) extends DeclarationStatement {
case class StructDefinitionStatement(name: String, fields: List[FieldDesc], alignment: Option[MemoryAlignment]) extends DeclarationStatement {
override def getAllExpressions: List[Expression] = Nil
}
case class UnionDefinitionStatement(name: String, fields: List[FieldDesc]) extends DeclarationStatement {
case class UnionDefinitionStatement(name: String, fields: List[FieldDesc], alignment: Option[MemoryAlignment]) extends DeclarationStatement {
override def getAllExpressions: List[Expression] = Nil
}

View File

@ -359,7 +359,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
for (item <- items) {
env.eval(item) match {
case Some(c) =>
for(i <- 0 until elementType.size) {
for(i <- 0 until elementType.alignedSize) {
writeByte(bank, index, subbyte(c, i, elementType.size))(None)
bank0.occupied(index) = true
bank0.initialized(index) = true
@ -544,11 +544,11 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
log.error(s"Preinitialized variable `$name` should be defined in the `default` bank")
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.alignedSize, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + typ.size)
rwDataEnd = rwDataEnd.max(index + typ.alignedSize)
}
val altName = m.name.stripPrefix(env.prefix) + "`"
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))

View File

@ -1,20 +1,38 @@
package millfork.output
import millfork.MathUtils
/**
* @author Karol Stasiak
*/
sealed trait MemoryAlignment {
def isMultiplePages: Boolean
def roundSizeUp(size: Int): Int
def &(other: MemoryAlignment): MemoryAlignment
}
case object NoAlignment extends MemoryAlignment {
override def isMultiplePages: Boolean = false
override def roundSizeUp(size: Int): Int = size
override def &(other: MemoryAlignment): MemoryAlignment = other
}
case object WithinPageAlignment extends MemoryAlignment {
override def isMultiplePages: Boolean = false
override def roundSizeUp(size: Int): Int = size
override def &(other: MemoryAlignment): MemoryAlignment = other match {
case NoAlignment | WithinPageAlignment => this
case _ => throw new IllegalArgumentException(s"Cannot use incompatible alignments $this and $other simultaneously")
}
}
case class DivisibleAlignment(divisor: Int) extends MemoryAlignment {
override def isMultiplePages: Boolean = divisor > 256
override def roundSizeUp(size: Int): Int =
if (size % divisor == 0) size else size + divisor - size % divisor
override def &(other: MemoryAlignment): MemoryAlignment = other match {
case NoAlignment => this
case DivisibleAlignment(d) => DivisibleAlignment(MathUtils.lcm(divisor, d))
case _ => throw new IllegalArgumentException(s"Cannot use incompatible alignments $this and $other simultaneously")
}
}

View File

@ -725,17 +725,19 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
p <- position()
_ <- "struct" ~ !letterOrDigit ~/ SWS ~ position("struct name")
name <- identifier ~/ HWS
_ <- position("struct defintion block")
align <- alignmentDeclaration(NoAlignment).? ~/ HWS
_ <- position("struct definition block")
fields <- compoundTypeFields ~/ Pass
} yield Seq(StructDefinitionStatement(name, fields).pos(p))
} yield Seq(StructDefinitionStatement(name, fields, align).pos(p))
val unionDefinition: P[Seq[UnionDefinitionStatement]] = for {
p <- position()
_ <- "union" ~ !letterOrDigit ~/ SWS ~ position("union name")
name <- identifier ~/ HWS
_ <- position("union defintion block")
align <- alignmentDeclaration(NoAlignment).? ~/ HWS
_ <- position("union definition block")
fields <- compoundTypeFields ~/ Pass
} yield Seq(UnionDefinitionStatement(name, fields).pos(p))
} yield Seq(UnionDefinitionStatement(name, fields, align).pos(p))
val segmentBlock: P[Seq[BankedDeclarationStatement]] = for {
(_, bankName) <- "segment" ~ AWS ~ "(" ~ AWS ~ position("segment name") ~ identifier ~ AWS ~ ")" ~ AWS ~ "{" ~/ AWS

View File

@ -641,4 +641,60 @@ class ArraySuite extends FunSuite with Matchers with AppendedClues {
| }
""".stripMargin)
}
test("Arrays of aligned structs") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Intel8080, Cpu.Z80, Cpu.Motorola6809)(
"""
| struct s align(2) { byte x }
| array(s) a[7]
| word output @$c000
| byte output2 @$c002
| void main () {
| output = a[1].pointer - a[0].pointer
| a[f(3)].x = 5
| output2 = a[3].x
| }
| noinline byte f(byte x) = x
""".stripMargin) { m =>
m.readWord(0xc000) should equal(2)
m.readByte(0xc002) should equal(5)
}
}
test("Accessing large fields of structs in arrays") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos/*, Cpu.Intel8080, Cpu.Z80, Cpu.Motorola6809*/)(
"""
| struct s { word x, word y}
| array(s) a[7]
| word output @$c000
|
| void main () {
| a[f(4)].y = 5
| a[f(4)].y = a[f(4)].y
| output = a[f(4)].y
| }
| noinline byte f(byte x) = x
""".stripMargin) { m =>
m.readByte(0xc000) should equal(5)
}
}
test("Accessing large fields of structs in arrays 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos/*, Cpu.Intel8080, Cpu.Z80, Cpu.Motorola6809*/)(
"""
| struct s { word x, word y}
| array(s) a[7] @$c000
|
| void main () {
| byte t
| t = f(4)
| a[t+4].x = t << 3
| a[t+4].y = t ^ 6
| }
| noinline byte f(byte x) = x
""".stripMargin) { m =>
m.readWord(0xc020) should equal(32)
m.readWord(0xc022) should equal(2)
}
}
}

View File

@ -0,0 +1,23 @@
package millfork.test
import millfork.MathUtils
import millfork.MathUtils.gcd
import org.scalatest.{AppendedClues, FunSuite, Matchers}
/**
* @author Karol Stasiak
*/
class MathUtilsSuite extends FunSuite with Matchers with AppendedClues {
test("GCD") {
gcd(2, 4) should equal(2)
gcd(4, 2) should equal(2)
gcd(5, 1) should equal(1)
gcd(5, 5) should equal(5)
gcd(0, 5) should equal(5)
gcd(5, 0) should equal(5)
gcd(9, 12) should equal(3)
gcd(12, 9) should equal(3)
}
}