1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-12-22 16:31:02 +00:00

Const arrays

This commit is contained in:
Karol Stasiak 2019-04-29 22:57:40 +02:00
parent 41e6bddfd9
commit d9f88cdfad
31 changed files with 207 additions and 74 deletions

View File

@ -22,6 +22,8 @@
* Arrays can now have elements of types other than `byte` (still limited in size to 1 byte though).
* Arrays can now be constant.
* Added hint for identifiers with typos.
* Aliases now also support subfields.

View File

@ -10,6 +10,8 @@ even up to hardware damage.
* array overruns: indexing past the end of an array leads to undefined behaviour
* writing to arrays defined as `const`
* stray pointers: indexing a pointer that doesn't point to a valid object or indexing it past the end of the pointed object leads to undefined behaviour
* reading uninitialized variables: will return undefined values

View File

@ -21,6 +21,8 @@ It is not allowed in any other places.
String literals can be used as either array initializers or expressions of type `pointer`.
String literals are equivalent to constanr arrays. Writing to them via their pointer is undefined behaviour.
If a string literal is used as an expression, then the text data will be located in the default code segment,
regardless of which code segment the current function is located it. This may be subject to change in future releases.

View File

@ -106,12 +106,14 @@ An array is a continuous sequence of bytes in memory.
Syntax:
`[segment(<segment>)] array [(<element type>)] <name> [[<size>]] [align ( <alignment> )] [@<address>] [= <initial_values>]`
`[segment(<segment>)] [const] array [(<element type>)] <name> [[<size>]] [align ( <alignment> )] [@<address>] [= <initial_values>]`
* `<segment>`: segment name; if absent,
then defaults to `default_code_segment` as defined for the platform if the array has initial values,
or to `default` if it doesn't.
* if `const` is present, the array is read-only. Read-only arrays have to have a fixed address and/or defined contents.
* `<element type>`: type of the elements of the array.
It must be of size 1 byte.
If omitted, the default is `byte`.

View File

@ -155,13 +155,13 @@ void createStarScreen() {
}
}
array starfieldCols = [
const array starfieldCols = [
14,10,12,15,14,13,12,11,10,14,
14,10,14,15,14,13,12,11,10,12
]
array starfieldRow = [
const array starfieldRow = [
058,092,073,064,091,062,093,081,066,094,
086,059,079,087,080,071,076,067,082,095,
100,078,099,060,075,063,084,065,083,096,

View File

@ -33,7 +33,7 @@ asm void stabilize(byte x){
RTS
}
array colours = [
const array colours = [
$06, $06, $06, $0e, $06, $0e,
$0e, $06, $0e, $0e, $0e, $03,
$0e, $03, $03, $0e, $03, $03,

View File

@ -72,13 +72,13 @@ void hardscroll() {
}
}
array scroll_text = [
const array scroll_text = [
"this is a simple scroller demo written in millfork. " scr,
"as you can see, you don't need assembly to do simple effects. " scr,
" " scr
]
array logo = [
const array logo = [
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
@ -106,7 +106,7 @@ array logo = [
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32
]
array logo_colours = [
const array logo_colours = [
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,

View File

@ -1,6 +1,6 @@
import stdio
array p = [
const array p = [
"this is an example", 13,
"of {red}multiline {yellow}{reverse}",
#if CBM
@ -11,7 +11,7 @@ array p = [
"{reverseoff} {white}text"
]
array s = [
const array s = [
"and this is an example " scr,
"of text done in screencodes" scr
]

View File

@ -87,7 +87,7 @@ void load_charset() {
}
}
array charset = [
const array charset = [
$00,$00,$00,$00,$00,$00,$00,$00,
$18,$18,$18,$18,$00,$00,$18,$00,
$66,$66,$66,$00,$00,$00,$00,$00,

View File

@ -136,7 +136,7 @@ void scroll_screen() {
}
}
array bg = [
const array bg = [
" " ascii,
"12345678901234567890123456789012" ascii,
" " ascii,
@ -174,13 +174,13 @@ array bg = [
]
array palette = [
const array palette = [
$0E,$00,$0E,$19,$00,$00,$00,$00,$00,$00,$00,$00,$01,$00,$01,$21,
$0E,$20,$22,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
]
segment(chrrom)
array charset @$0200 = [
const array charset @$0200 = [
$00,$00,$00,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$18,$18,$18,$18,$00,$00,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$66,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
@ -248,7 +248,7 @@ array charset @$0200 = [
]
segment(chrrom)
array sprites @$1000 = [
const array sprites @$1000 = [
$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,
$18,$24,$66,$99,$99,$66,$24,$18,$00,$18,$18,$66,$66,$18,$18,$00
]

View File

@ -151,7 +151,7 @@ noinline void scroll_screen() {
}
}
array bg = [
const array bg = [
" " ascii,
"12345678901234567890123456789012" ascii,
" " ascii,
@ -189,13 +189,13 @@ array bg = [
]
array palette = [
const array palette = [
$0E,$00,$0E,$19,$00,$00,$00,$00,$00,$00,$00,$00,$01,$00,$01,$21,
$0E,$20,$22,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
]
segment(chrrom0)
array charset @$6200 = [
const array charset @$6200 = [
$00,$00,$00,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$18,$18,$18,$18,$00,$00,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$66,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
@ -263,7 +263,7 @@ array charset @$6200 = [
]
segment(chrrom1)
array sprites @$A000 = [
const array sprites @$A000 = [
$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,
$18,$24,$66,$99,$99,$66,$24,$18,$00,$18,$18,$66,$66,$18,$18,$00
]

View File

@ -122,9 +122,11 @@ object Main {
Files.write(path, code)
}
errorReporting.debug(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms")
c.runFileName.foreach(program =>
new ProcessBuilder(program, Paths.get(defaultPrgOutput).toAbsolutePath.toString).start()
)
c.runFileName.foreach{ program =>
val outputAbsolutePath = Paths.get(defaultPrgOutput).toAbsolutePath.toString
errorReporting.debug(s"Running: $program $outputAbsolutePath")
new ProcessBuilder(program, outputAbsolutePath).start()
}
if (platform.generateBbcMicroInfFile) {
val start = platform.codeAllocators("default").startAt
val codeLength = result.code("default").length

View File

@ -9,6 +9,7 @@ import millfork.assembly.mos.{AddrMode, opt, _}
import millfork.assembly.mos.AddrMode._
import millfork.env._
import millfork.error.FatalErrorReporting
import millfork.node.LiteralExpression
/**
* These optimizations should not remove opportunities for more complex optimizations to trigger.
@ -1063,6 +1064,30 @@ object AlwaysGoodOptimizations {
(HasOpcodeIn(LAX, LDA) & RefersTo("__sp", 0)
& XContainsSoftwareStackPointer) ~~> (_ => List(AssemblyLine.implied(TXA))),
(Elidable & HasOpcodeIn(LDA, LDX, LDY, ADC, SBC, EOR, AND, ORA) & HasAddrModeIn(Absolute, ZeroPage, LongAbsolute) & HasParameterWhere{
case MemoryAddressConstant(i: InitializedArray) => i.readOnly
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(i: InitializedArray), NumericConstant(offset, _)) =>
i.readOnly && offset >= 0 && offset < i.sizeInBytes
case CompoundConstant(MathOperator.Plus, NumericConstant(offset, _), MemoryAddressConstant(i: InitializedArray)) =>
i.readOnly && offset >= 0 && offset < i.sizeInBytes
case _ => false
}) ~~> { code =>
val p = code.head.parameter match {
case MemoryAddressConstant(i: InitializedArray) =>
i.contents.head
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(i: InitializedArray), NumericConstant(offset, _)) =>
i.contents(offset.toInt)
case CompoundConstant(MathOperator.Plus, NumericConstant(offset, _), MemoryAddressConstant(i: InitializedArray)) =>
i.contents(offset.toInt)
}
p match {
case LiteralExpression(n, s) =>
code.head.copy(addrMode = Immediate, parameter = NumericConstant(n, s)) :: Nil
case _ =>
code
}
},
))
val PointlessStackStore = new RuleBasedAssemblyOptimization("Pointless stack store",

View File

@ -3,8 +3,8 @@ package millfork.assembly.z80.opt
import millfork.assembly.AssemblyOptimization
import millfork.assembly.z80.{opt, _}
import millfork.assembly.z80.ZOpcode._
import millfork.env.{CompoundConstant, Constant, MathOperator, NumericConstant}
import millfork.node.ZRegister
import millfork.env.{CompoundConstant, Constant, InitializedArray, MathOperator, MemoryAddressConstant, NumericConstant}
import millfork.node.{LiteralExpression, ZRegister}
import ZRegister._
import millfork.DecimalUtils._
import millfork.error.FatalErrorReporting
@ -131,6 +131,30 @@ object AlwaysGoodI80Optimizations {
code.init :+ ZLine.ldImm16(register, hi.<<(8) + lo)
}
),
(Elidable & HasOpcode(LD) & HasRegisters(TwoRegisters(A, MEM_ABS_8)) & HasParameterWhere {
case MemoryAddressConstant(i: InitializedArray) => i.readOnly
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(i: InitializedArray), NumericConstant(offset, _)) =>
i.readOnly && offset >= 0 && offset < i.sizeInBytes
case CompoundConstant(MathOperator.Plus, NumericConstant(offset, _), MemoryAddressConstant(i: InitializedArray)) =>
i.readOnly && offset >= 0 && offset < i.sizeInBytes
case _ => false
}) ~~> { code =>
val p = code.head.parameter match {
case MemoryAddressConstant(i: InitializedArray) =>
i.contents.head
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(i: InitializedArray), NumericConstant(offset, _)) =>
i.contents(offset.toInt)
case CompoundConstant(MathOperator.Plus, NumericConstant(offset, _), MemoryAddressConstant(i: InitializedArray)) =>
i.contents(offset.toInt)
}
p match {
case LiteralExpression(n, s) =>
code.head.copy(registers = TwoRegisters(A, IMM_8), parameter = NumericConstant(n, s)) :: Nil
case _ =>
code
}
},
)
val PointlessLoad = new RuleBasedAssemblyOptimization("Pointless load",

View File

@ -612,6 +612,12 @@ case class MatchParameter(i: Int) extends AssemblyLinePattern {
override def hitRate: Double = 0.929
}
case class HasParameterWhere(predicate: Constant => Boolean) extends TrivialAssemblyLinePattern {
override def apply(line: ZLine): Boolean = predicate(line.parameter)
override def hitRate: Double = 0.332
}
case class IsLabelMatching(i: Int) extends AssemblyLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean =
line.opcode == ZOpcode.LABEL && ctx.addObject(i, line.parameter.quickSimplify)

View File

@ -155,7 +155,7 @@ abstract class AbstractReturnDispatch[T <: AbstractCode] {
val a = InitializedArray(label + "$" + ix + ".array", None, (paramMins(ix) to paramMaxes(ix)).map { key =>
map(key)._2.lift(ix).getOrElse(LiteralExpression(0, 1))
}.toList,
ctx.function.declaredBank, b, b, NoAlignment)
ctx.function.declaredBank, b, b, readOnly = true, NoAlignment)
env.registerUnnamedArray(a)
a
}

View File

@ -338,7 +338,7 @@ abstract class AbstractStatementPreprocessor(ctx: CompilationContext, statements
case TextLiteralExpression(characters) =>
val name = genName(characters)
if (ctx.env.maybeGet[Thing](name).isEmpty) {
ctx.env.root.registerArray(ArrayDeclarationStatement(name, None, None, "byte", None, Some(LiteralContents(characters)), None).pos(pos), ctx.options)
ctx.env.root.registerArray(ArrayDeclarationStatement(name, None, None, "byte", None, const = true, Some(LiteralContents(characters)), None).pos(pos), ctx.options)
}
VariableExpression(name).pos(pos)
case VariableExpression(v) if currentVarValues.contains(v) =>

View File

@ -191,12 +191,12 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
val reg = ctx.env.get[VariableInMemory]("__reg")
val compileIndex = compile(ctx, indexExpression, Some(MosExpressionCompiler.getExpressionType(ctx, indexExpression) -> RegisterVariable(MosRegister.YA, w)), BranchSpec.None)
val prepareRegister = pointy match {
case ConstantPointy(addr, _, _, _, _, _) =>
case p:ConstantPointy =>
List(
AssemblyLine.implied(CLC),
AssemblyLine.immediate(ADC, addr.hiByte),
AssemblyLine.immediate(ADC, p.value.hiByte),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.immediate(LDA, addr.loByte),
AssemblyLine.immediate(LDA, p.value.loByte),
AssemblyLine.zeropage(STA, reg))
case VariablePointy(addr, _, _, true) =>
List(
@ -323,6 +323,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
(pointy, variableIndex, variableIndexSize, totalIndexSize) match {
case (p: ConstantPointy, None, _, _) =>
if (p.readOnly) {
ctx.log.error("Writing to a constant array", target.position)
}
List(AssemblyLine.absolute(store, env.genRelativeVariable(p.value + constIndex, b, zeropage = false)))
case (p: VariablePointy, _, _, 2) =>
wrapWordIndexingStorage(prepareWordIndexing(ctx, p, indexExpr))
@ -330,9 +333,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
// TODO: optimize?
wrapWordIndexingStorage(prepareWordIndexing(ctx, p, indexExpr))
case (p: ConstantPointy, Some(v), 2, _) =>
if (p.readOnly) {
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), v))
wrapWordIndexingStorage(prepareWordIndexing(ctx, ConstantPointy(p.value + constIndex, None, if (constIndex.isProvablyZero) p.size 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)
}
storeToArrayAtUnknownIndex(v, p.value)
//TODO: should there be a type check or a zeropage check?
case (pointerVariable@VariablePointy(varAddr, _, _, true), None, _, 0 | 1) =>
@ -801,7 +810,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
None,
if (constantIndex.isProvablyZero) a.size else None,
env.get[VariableType]("word"),
a.elementType, NoAlignment), v) ++ loadFromReg()
a.elementType, NoAlignment, a.readOnly), v) ++ loadFromReg()
case (a: VariablePointy, _, 2, _) =>
prepareWordIndexing(ctx, a, indexExpr) ++ loadFromReg()
case (p: VariablePointy, _, 0 | 1, _) if !p.zeropage =>

View File

@ -41,7 +41,7 @@ object MosReturnDispatch extends AbstractReturnDispatch[AssemblyLine] {
}
if (useJmpaix) {
val jumpTable = InitializedArray(label + "$jt.array", None, (actualMin to actualMax).flatMap(i => List(lobyte0(map(i)._1), hibyte0(map(i)._1))).toList, ctx.function.declaredBank, b, b, NoAlignment)
val jumpTable = InitializedArray(label + "$jt.array", None, (actualMin to actualMax).flatMap(i => List(lobyte0(map(i)._1), hibyte0(map(i)._1))).toList, ctx.function.declaredBank, b, b, readOnly = true, NoAlignment)
env.registerUnnamedArray(jumpTable)
if (copyParams.isEmpty) {
val loadIndex = MosExpressionCompiler.compile(ctx, stmt.indexer, Some(b -> RegisterVariable(MosRegister.A, b)), BranchSpec.None)
@ -56,8 +56,8 @@ object MosReturnDispatch extends AbstractReturnDispatch[AssemblyLine] {
}
} else {
val loadIndex = MosExpressionCompiler.compile(ctx, stmt.indexer, Some(b -> RegisterVariable(MosRegister.X, b)), BranchSpec.None)
val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => lobyte1(map(i)._1)).toList, ctx.function.declaredBank, b, b, NoAlignment)
val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => hibyte1(map(i)._1)).toList, ctx.function.declaredBank, b, b, NoAlignment)
val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => lobyte1(map(i)._1)).toList, ctx.function.declaredBank, b, b, readOnly = true, NoAlignment)
val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => hibyte1(map(i)._1)).toList, ctx.function.declaredBank, b, b, readOnly = true, NoAlignment)
env.registerUnnamedArray(jumpTableLo)
env.registerUnnamedArray(jumpTableHi)
val actualJump = if (ctx.options.flag(CompilationFlag.LUnixRelocatableCode)) {

View File

@ -23,7 +23,7 @@ object Z80BulkMemoryOperations {
val sourceOffset = removeVariableOnce(f.variable, source.index).getOrElse(return compileForStatement(ctx, f)._1)
if (!sourceOffset.isPure) return compileForStatement(ctx, f)._1
val sourceIndexExpression = SumExpression(List(false -> sourceOffset, false -> f.start), decimal = false)
val calculateSource = Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(source.name, sourceIndexExpression))
val calculateSource = Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(source.name, sourceIndexExpression).pos(source.position), forWriting = false)
compileMemoryBulk(ctx, target, f,
useDEForTarget = true,
preferDecreasing = false,
@ -51,7 +51,7 @@ object Z80BulkMemoryOperations {
case _ => SumExpression(List(false -> targetOffset, false -> f.start), decimal = false)
}
val array = if (target.name != f.variable) target.name else "$0000"
val calculateAddress = Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(array, targetIndexExpression))
val calculateAddress = Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(array, targetIndexExpression).pos(targetIndexExpression.position), forWriting = true)
val calculateSize = f.direction match {
case ForDirection.DownTo =>
Z80ExpressionCompiler.stashHLIfChanged(ctx, Z80ExpressionCompiler.compileToBC(ctx, SumExpression(List(false -> f.start, true -> f.end), decimal = false)))
@ -180,7 +180,7 @@ object Z80BulkMemoryOperations {
useDEForTarget = true,
preferDecreasing = true,
isSmall => if (isSmall) {
Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(targetForHL.name, targetForHLIndexExpression)) -> c.initC
Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(targetForHL.name, targetForHLIndexExpression).pos(targetForHLIndexExpression.position), forWriting = false) -> c.initC
} else break,
next => loads ++ (c.nextC :+ ZLine.register(next, ZRegister.HL)),
_ => None
@ -424,7 +424,7 @@ object Z80BulkMemoryOperations {
}
val next = if (decreasing) DEC_16 else INC_16
val calculateSourceValue = loadA(next)
val calculateTargetAddress = Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(target.name, targetIndexExpression))
val calculateTargetAddress = Z80ExpressionCompiler.calculateAddressToHL(ctx, IndexedExpression(target.name, targetIndexExpression).pos(targetIndexExpression.position), forWriting = true)
val extraInitializationPair = extraAddressCalculations(smallCount)
// TODO: figure the optimal compilation order
val loading = if (useDEForTarget) {

View File

@ -446,7 +446,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
}
}
case i: IndexedExpression =>
calculateAddressToHL(ctx, i) match {
calculateAddressToHL(ctx, i, forWriting = false) match {
case List(ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), addr)) => loadByte(addr, target, volatile = false)
case code => code ++ loadByteViaHL(target)
}
@ -841,7 +841,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
calculateAddressToAppropriatePointer(ctx, l) match {
calculateAddressToAppropriatePointer(ctx, l, forWriting = true) match {
case Some((lvo, code)) =>
code ++
(ZLine.ld8(ZRegister.A, lvo) ::
@ -858,7 +858,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
calculateAddressToAppropriatePointer(ctx, l) match {
calculateAddressToAppropriatePointer(ctx, l, forWriting = true) match {
case Some((lvo, code)) =>
code ++
(ZLine.ld8(ZRegister.A, lvo) ::
@ -883,7 +883,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case "*'=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
calculateAddressToAppropriatePointer(ctx, l) match {
calculateAddressToAppropriatePointer(ctx, l, forWriting = true) match {
case Some((lvo, code)) =>
code ++
(ZLine.ld8(ZRegister.A, lvo) ::
@ -1003,7 +1003,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
}
def calculateLoadAndStoreForByte(ctx: CompilationContext, expr: LhsExpression): (List[ZLine], List[ZLine]) = {
Z80ExpressionCompiler.calculateAddressToAppropriatePointer(ctx, expr) match {
Z80ExpressionCompiler.calculateAddressToAppropriatePointer(ctx, expr, forWriting = true) match { // TODO: forWriting?
case Some((LocalVariableAddressViaHL, calculate)) =>
(calculate :+ ZLine.ld8(ZRegister.A, ZRegister.MEM_HL)) -> List(ZLine.ld8(ZRegister.MEM_HL, ZRegister.A))
case Some((LocalVariableAddressViaIX(offset), calculate)) =>
@ -1020,7 +1020,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
}
}
def calculateAddressToAppropriatePointer(ctx: CompilationContext, expr: LhsExpression): Option[(LocalVariableAddressOperand, List[ZLine])] = {
def calculateAddressToAppropriatePointer(ctx: CompilationContext, expr: LhsExpression, forWriting: Boolean): Option[(LocalVariableAddressOperand, List[ZLine])] = {
val env = ctx.env
expr match {
case VariableExpression(name) =>
@ -1035,7 +1035,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
Some(LocalVariableAddressViaHL -> calculateStackAddressToHL(ctx, v))
}
}
case i:IndexedExpression => Some(LocalVariableAddressViaHL -> calculateAddressToHL(ctx, i))
case i:IndexedExpression => Some(LocalVariableAddressViaHL -> calculateAddressToHL(ctx, i, forWriting))
case i:DerefExpression => Some(LocalVariableAddressViaHL -> compileDerefPointer(ctx, i))
case _:SeparateBytesExpression => None
case _ => ???
@ -1055,12 +1055,15 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
}
}
def calculateAddressToHL(ctx: CompilationContext, i: IndexedExpression): List[ZLine] = {
def calculateAddressToHL(ctx: CompilationContext, i: IndexedExpression, forWriting: Boolean): List[ZLine] = {
val env = ctx.env
val pointy = env.getPointy(i.name)
AbstractExpressionCompiler.checkIndexType(ctx, pointy, i.index)
pointy match {
case ConstantPointy(baseAddr, _, size, _, _, alignment) =>
case ConstantPointy(baseAddr, _, size, _, _, 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 (Some(index), offset) =>
@ -1324,7 +1327,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
}
}
case i:IndexedExpression =>
calculateAddressToHL(ctx, i) match {
calculateAddressToHL(ctx, i, forWriting = true) match {
case List(ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), addr)) => storeA(ctx, addr, 1, signedSource)
case code => if (code.exists(changesA)) {
List(ZLine.ld8(ZRegister.E, ZRegister.A)) ++ stashDEIfChanged(ctx, code) :+ ZLine.ld8(ZRegister.MEM_HL, ZRegister.E)

View File

@ -51,8 +51,8 @@ object Z80ReturnDispatch extends AbstractReturnDispatch[ZLine] {
}
val copyParams = pair._2.reverse.flatten
val offsetAfterParams = pair._1
val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => lobyte0(map(i)._1)).toList, ctx.function.declaredBank, b, b, NoAlignment)
val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => hibyte0(map(i)._1)).toList, ctx.function.declaredBank, b, b, NoAlignment)
val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => lobyte0(map(i)._1)).toList, ctx.function.declaredBank, b, b, readOnly = true, NoAlignment)
val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => hibyte0(map(i)._1)).toList, ctx.function.declaredBank, b, b, readOnly = true, NoAlignment)
env.registerUnnamedArray(jumpTableLo)
env.registerUnnamedArray(jumpTableHi)

View File

@ -70,7 +70,7 @@ object Z80Shifting {
} else if (i >= 8) {
ZLine.ldImm8(ZRegister.A, 0) :: Z80ExpressionCompiler.storeA(ctx, lhs, signedSource = false)
} else {
Z80ExpressionCompiler.calculateAddressToAppropriatePointer(ctx, lhs) match {
Z80ExpressionCompiler.calculateAddressToAppropriatePointer(ctx, lhs, forWriting = true) match {
case Some((register, l)) =>
// for shifting left:
// ADD A = 4 cycles

View File

@ -318,7 +318,7 @@ object ZBuiltIns {
}
def perform8BitInPlace(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, opcode: ZOpcode.Value, decimal: Boolean = false): List[ZLine] = {
val (lv, calculateAddress):(LocalVariableAddressOperand, List[ZLine]) = Z80ExpressionCompiler.calculateAddressToAppropriatePointer(ctx, lhs).getOrElse{
val (lv, calculateAddress):(LocalVariableAddressOperand, List[ZLine]) = Z80ExpressionCompiler.calculateAddressToAppropriatePointer(ctx, lhs, forWriting = true).getOrElse{
ctx.log.error("Invalid left-hand-side expression", lhs.position)
LocalVariableAddressViaHL -> Nil
}

View File

@ -85,7 +85,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def getAllFixedAddressObjects: List[(String, Int, Int)] = {
things.values.flatMap {
case RelativeArray(_, NumericConstant(addr, _), size, declaredBank, _, _) =>
case RelativeArray(_, NumericConstant(addr, _), size, declaredBank, _, _, _) =>
List((declaredBank.getOrElse("default"), addr.toInt, size))
case RelativeVariable(_, NumericConstant(addr, _), typ, _, declaredBank, _) =>
List((declaredBank.getOrElse("default"), addr.toInt, typ.size))
@ -374,13 +374,13 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
InitializedMemoryVariable
UninitializedMemoryVariable
getArrayOrPointer(name) match {
case th@InitializedArray(_, _, cs, _, i, e, _) => ConstantPointy(th.toAddress, Some(name), Some(cs.length), i, e, th.alignment)
case th@UninitializedArray(_, size, _, i, e, _) => ConstantPointy(th.toAddress, Some(name), Some(size), i, e, th.alignment)
case th@RelativeArray(_, _, size, _, i, e) => ConstantPointy(th.toAddress, Some(name), Some(size), i, e, NoAlignment)
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 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)
ConstantPointy(value, 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")
@ -393,7 +393,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)
ConstantPointy(Constant.Zero, None, None, w, b, NoAlignment, readOnly = false)
}
}
@ -597,7 +597,18 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case Some(m) if m.contains(name) => Some(m(name))
case _ => maybeGet[ConstantThing](name).map(_.value)
}
case IndexedExpression(_, _) => None
case IndexedExpression(arrName, index) =>
getPointy(arrName) match {
case ConstantPointy(MemoryAddressConstant(arr:InitializedArray), _, _, _, _, _, _) if arr.readOnly && arr.elementType.size == 1 =>
eval(index).flatMap {
case NumericConstant(constIndex, _) =>
if (constIndex >= 0 && constIndex < arr.sizeInBytes) {
eval(arr.contents(constIndex.toInt))
} else None
case _ => None
}
case _ => None
}
case _: DerefExpression => None
case _: IndirectFieldExpression => None
case _: DerefDebuggingExpression => None
@ -1206,6 +1217,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
stmt.elements match {
case None =>
if (stmt.const && stmt.address.isEmpty) {
log.error(s"Constant array `${stmt.name}` without contents nor address", stmt.position)
}
stmt.length match {
case None => log.error(s"Array `${stmt.name}` without size nor contents", stmt.position)
case Some(l) =>
@ -1230,9 +1244,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val alignment = stmt.alignment.getOrElse(defaultArrayAlignment(options, length))
val array = address match {
case None => UninitializedArray(stmt.name + ".array", length.toInt,
declaredBank = stmt.bank, indexType, e, alignment)
declaredBank = stmt.bank, indexType, e, stmt.const, alignment)
case Some(aa) => RelativeArray(stmt.name + ".array", aa, length.toInt,
declaredBank = stmt.bank, indexType, e)
declaredBank = stmt.bank, indexType, e, stmt.const)
}
addThing(array, stmt.position)
registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, stmt.bank, alignment, isVolatile = false), stmt.position, options, Some(e))
@ -1266,6 +1280,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
case Some(contents1) =>
if (!stmt.const && options.flag(CompilationFlag.ReadOnlyArrays)) {
log.warn(s"Initialized array `${stmt.name}` is not defined as const, but the target platform doesn't support writable initialized arrays.", stmt.position)
}
val contents = extractArrayContents(contents1)
val indexType = stmt.length match {
case None => // array arr = [...]
@ -1304,7 +1321,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
for (element <- contents) {
AbstractExpressionCompiler.checkAssignmentType(this, element, e)
}
val array = InitializedArray(stmt.name + ".array", address, contents, declaredBank = stmt.bank, indexType, e, alignment)
val array = InitializedArray(stmt.name + ".array", address, contents, declaredBank = stmt.bank, indexType, e, readOnly = stmt.const, alignment)
addThing(array, stmt.position)
registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None,
declaredBank = stmt.bank, alignment, isVolatile = false), stmt.position, options, Some(e))
@ -1587,7 +1604,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val b = get[VariableType]("byte")
val v = get[Type]("void")
if (options.flag(CompilationFlag.OptimizeForSonicSpeed)) {
addThing(InitializedArray("identity$", None, List.tabulate(256)(n => LiteralExpression(n, 1)), declaredBank = None, b, b, defaultArrayAlignment(options, 256)), None)
addThing(InitializedArray("identity$", None, IndexedSeq.tabulate(256)(n => LiteralExpression(n, 1)), declaredBank = None, b, b, readOnly = true, defaultArrayAlignment(options, 256)), None)
}
program.declarations.foreach {
case a: AliasDefinitionStatement => registerAlias(a)
@ -1629,12 +1646,12 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
if (CpuFamily.forType(options.platform.cpu) == CpuFamily.M6502) {
if (!things.contains("__constant8")) {
things("__constant8") = InitializedArray("__constant8", None, List(LiteralExpression(8, 1)), declaredBank = None, b, b, NoAlignment)
things("__constant8") = InitializedArray("__constant8", None, List(LiteralExpression(8, 1)), declaredBank = None, b, b, readOnly = true, NoAlignment)
}
if (options.flag(CompilationFlag.SoftwareStack)) {
if (!things.contains("__sp")) {
things("__sp") = UninitializedMemoryVariable("__sp", b, VariableAllocationMethod.Auto, None, NoAlignment, isVolatile = false)
things("__stack") = UninitializedArray("__stack", 256, None, b, b, WithinPageAlignment)
things("__stack") = UninitializedArray("__stack", 256, None, b, b, readOnly = false, WithinPageAlignment)
}
}
}

View File

@ -6,14 +6,23 @@ trait Pointy {
def name: Option[String]
def indexType: VariableType
def elementType: VariableType
def readOnly: Boolean
}
case class StackVariablePointy(offset: Int, indexType: VariableType, elementType: VariableType) extends Pointy {
override def name: Option[String] = None
override def readOnly: Boolean = false
}
case class VariablePointy(addr: Constant, indexType: VariableType, elementType: VariableType, zeropage: Boolean) extends Pointy {
override def name: Option[String] = None
override def readOnly: Boolean = false
}
case class ConstantPointy(value: Constant, name: Option[String], size: Option[Int], indexType: VariableType, elementType: VariableType, alignment: MemoryAlignment) extends Pointy
case class ConstantPointy(value: Constant,
name: Option[String],
size: Option[Int],
indexType: VariableType,
elementType: VariableType,
alignment: MemoryAlignment,
override val readOnly: Boolean) extends Pointy

View File

@ -266,9 +266,10 @@ trait MfArray extends ThingInMemory with IndexableThing {
override def isVolatile: Boolean = false
/* TODO: what if larger elements? */
def sizeInBytes: 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 alignment: MemoryAlignment) extends MfArray with UninitializedMemory {
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 {
override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this)
override def alloc: VariableAllocationMethod.Value = VariableAllocationMethod.Static
@ -280,7 +281,7 @@ case class UninitializedArray(name: String, /* TODO: what if larger elements? */
override def zeropage: Boolean = false
}
case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType) extends MfArray {
case class RelativeArray(name: String, address: Constant, sizeInBytes: 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)
@ -290,7 +291,7 @@ case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, decl
override def zeropage: Boolean = false
}
case class InitializedArray(name: String, address: Option[Constant], contents: List[Expression], declaredBank: Option[String], indexType: VariableType, elementType: VariableType, override val alignment: MemoryAlignment) extends MfArray with PreallocableThing {
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 {
override def shouldGenerate = true
override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false)

View File

@ -366,6 +366,7 @@ case class ArrayDeclarationStatement(name: String,
length: Option[Expression],
elementType: String,
address: Option[Expression],
const: Boolean,
elements: Option[ArrayContents],
alignment: Option[MemoryAlignment]) extends DeclarationStatement {
override def getAllExpressions: List[Expression] = List(length, address).flatten ++ elements.fold(List[Expression]())(_.getAllExpressions)

View File

@ -253,7 +253,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, _, _, _, _) =>
case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _, _, _, _, _) =>
val bank = thing.bank(options)
val bank0 = mem.banks(bank)
var index = address.toInt
@ -277,7 +277,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}).mkString(", "))
}
initializedVariablesSize += items.length
case thing@InitializedArray(name, Some(_), items, _, _, _, _) => ???
case thing@InitializedArray(name, Some(_), items, _, _, _, _, _) => ???
case f: NormalFunction if f.address.isDefined =>
val bank = f.bank(options)
val bank0 = mem.banks(bank)
@ -357,7 +357,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
justAfterCode += "default" -> (index + 1)
}
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
case thing@InitializedArray(name, None, items, _, _, _, alignment) =>
case thing@InitializedArray(name, None, items, _, _, _, _, alignment) =>
val bank = thing.bank(options)
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, items.size, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)

View File

@ -238,6 +238,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
val arrayDefinition: P[Seq[ArrayDeclarationStatement]] = for {
p <- position()
bank <- bankDeclaration
const <- ("const".! ~ HWS).?
_ <- "array" ~ !letterOrDigit
elementType <- ("(" ~/ AWS ~/ identifier ~ AWS ~ ")").? ~/ HWS
name <- identifier ~ HWS
@ -245,7 +246,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
alignment <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ HWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).? ~/ HWS
contents <- ("=" ~/ HWS ~/ arrayContents).? ~/ HWS
} yield Seq(ArrayDeclarationStatement(name, bank, length, elementType.getOrElse("byte"), addr, contents, alignment).pos(p))
} yield Seq(ArrayDeclarationStatement(name, bank, length, elementType.getOrElse("byte"), addr, const.isDefined, contents, alignment).pos(p))
def tightMfExpression(allowIntelHex: Boolean, allowTopLevelIndexing: Boolean): P[Expression] = {
val a = if (allowIntelHex) atomWithIntel else atom
@ -322,8 +323,8 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
fieldPath <- (HWS ~ "->" ~/ AWS ~/ identifier ~/ index.rep).rep
} yield (expr, firstIndices, fieldPath) match {
case (_, Seq(), Seq()) => expr
case (VariableExpression(vname), Seq(i), Seq()) => IndexedExpression(vname, i).asInstanceOf[T]
case _ => IndirectFieldExpression(expr, firstIndices, fieldPath).asInstanceOf[T]
case (VariableExpression(vname), Seq(i), Seq()) => IndexedExpression(vname, i).pos(expr.position).asInstanceOf[T]
case _ => IndirectFieldExpression(expr, firstIndices, fieldPath).pos(expr.position).asInstanceOf[T]
}
// def mfLhsExpression: P[LhsExpression] = for {

View File

@ -270,4 +270,31 @@ class ArraySuite extends FunSuite with Matchers {
m.readByte(0xc000) should equal(0x44)
}
}
test("Const arrays") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(
"""
| const array square = [0, 1, 4, 9, 16, 25, 36, 49, 64]
| byte five() = 5
| byte output0 @$c000
| byte output1 @$c001
| void main () {
| output0 = square[3]
| output1 = square[five()]
| }
""".stripMargin) { m =>
m.readByte(0xc000) should equal(9)
m.readByte(0xc001) should equal(25)
}
}
test("Writing to const arrays should not compile") {
ShouldNotCompile(
"""
| const array a = [0]
| void main () {
| a[0] = 5
| }
""".stripMargin)
}
}