diff --git a/CHANGELOG.md b/CHANGELOG.md index f82034b1..e238b912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/docs/abi/undefined-behaviour.md b/docs/abi/undefined-behaviour.md index 9180b2d7..d253b61d 100644 --- a/docs/abi/undefined-behaviour.md +++ b/docs/abi/undefined-behaviour.md @@ -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 diff --git a/docs/lang/literals.md b/docs/lang/literals.md index 561e55a5..99168119 100644 --- a/docs/lang/literals.md +++ b/docs/lang/literals.md @@ -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. diff --git a/docs/lang/syntax.md b/docs/lang/syntax.md index f8be2a5f..def0f176 100644 --- a/docs/lang/syntax.md +++ b/docs/lang/syntax.md @@ -106,12 +106,14 @@ An array is a continuous sequence of bytes in memory. Syntax: -`[segment()] array [()] [[]] [align ( )] [@
] [= ]` +`[segment()] [const] array [()] [[]] [align ( )] [@
] [= ]` * ``: 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. + * ``: type of the elements of the array. It must be of size 1 byte. If omitted, the default is `byte`. diff --git a/examples/c64/galencia.mfk b/examples/c64/galencia.mfk index 58049add..02ce6177 100644 --- a/examples/c64/galencia.mfk +++ b/examples/c64/galencia.mfk @@ -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, diff --git a/examples/c64/rasterbar.mfk b/examples/c64/rasterbar.mfk index 7d203c7b..6183c0e6 100644 --- a/examples/c64/rasterbar.mfk +++ b/examples/c64/rasterbar.mfk @@ -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, diff --git a/examples/c64/softscroll.mfk b/examples/c64/softscroll.mfk index 7909fbc0..ec65e21b 100644 --- a/examples/c64/softscroll.mfk +++ b/examples/c64/softscroll.mfk @@ -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, diff --git a/examples/crossplatform/text_encodings.mfk b/examples/crossplatform/text_encodings.mfk index 2c0ec6cf..8d4b402d 100644 --- a/examples/crossplatform/text_encodings.mfk +++ b/examples/crossplatform/text_encodings.mfk @@ -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 ] diff --git a/examples/gb/gbtest.mfk b/examples/gb/gbtest.mfk index 95dd71b0..492802a0 100644 --- a/examples/gb/gbtest.mfk +++ b/examples/gb/gbtest.mfk @@ -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, diff --git a/examples/nes/nestest.mfk b/examples/nes/nestest.mfk index eafe1ea3..e7cdc7e9 100644 --- a/examples/nes/nestest.mfk +++ b/examples/nes/nestest.mfk @@ -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 ] \ No newline at end of file diff --git a/examples/nes/nestest_mmc4.mfk b/examples/nes/nestest_mmc4.mfk index 80946510..46ef29e9 100644 --- a/examples/nes/nestest_mmc4.mfk +++ b/examples/nes/nestest_mmc4.mfk @@ -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 ] \ No newline at end of file diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 7fd6ed44..52eb2f4f 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -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 diff --git a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala index 7e184c22..4fe0f0b0 100644 --- a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala @@ -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", diff --git a/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala b/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala index c68fda37..f0c95a6c 100644 --- a/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala +++ b/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala @@ -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", diff --git a/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala index 5e84cabb..684ee34c 100644 --- a/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala @@ -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) diff --git a/src/main/scala/millfork/compiler/AbstractReturnDispatch.scala b/src/main/scala/millfork/compiler/AbstractReturnDispatch.scala index e5129510..8cecf60f 100644 --- a/src/main/scala/millfork/compiler/AbstractReturnDispatch.scala +++ b/src/main/scala/millfork/compiler/AbstractReturnDispatch.scala @@ -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 } diff --git a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala index e91ed756..a8ac7fca 100644 --- a/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala +++ b/src/main/scala/millfork/compiler/AbstractStatementPreprocessor.scala @@ -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) => diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index 75d9d8a8..0f6601ab 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -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 => diff --git a/src/main/scala/millfork/compiler/mos/MosReturnDispatch.scala b/src/main/scala/millfork/compiler/mos/MosReturnDispatch.scala index f9345c20..545cdf8e 100644 --- a/src/main/scala/millfork/compiler/mos/MosReturnDispatch.scala +++ b/src/main/scala/millfork/compiler/mos/MosReturnDispatch.scala @@ -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)) { diff --git a/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala b/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala index fe9a8562..58fb9dfb 100644 --- a/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala +++ b/src/main/scala/millfork/compiler/z80/Z80BulkMemoryOperations.scala @@ -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) { diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index ce8e6385..ecf1fd00 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -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) diff --git a/src/main/scala/millfork/compiler/z80/Z80ReturnDispatch.scala b/src/main/scala/millfork/compiler/z80/Z80ReturnDispatch.scala index bb4a8a09..5ebc1619 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ReturnDispatch.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ReturnDispatch.scala @@ -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) diff --git a/src/main/scala/millfork/compiler/z80/Z80Shifting.scala b/src/main/scala/millfork/compiler/z80/Z80Shifting.scala index ff72a33a..493c0112 100644 --- a/src/main/scala/millfork/compiler/z80/Z80Shifting.scala +++ b/src/main/scala/millfork/compiler/z80/Z80Shifting.scala @@ -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 diff --git a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala index d12d92d1..a11de97a 100644 --- a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala +++ b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala @@ -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 } diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 5a3b4d84..89610e8b 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -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) } } } diff --git a/src/main/scala/millfork/env/Pointy.scala b/src/main/scala/millfork/env/Pointy.scala index ceceea6e..700f6644 100644 --- a/src/main/scala/millfork/env/Pointy.scala +++ b/src/main/scala/millfork/env/Pointy.scala @@ -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 diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 73cb9f6a..1fc6ee53 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -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) diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index a35ff9a4..809a1de2 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -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) diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index 29bd4548..2ac0f842 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -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) diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index 2f7e267d..d0124289 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -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 { diff --git a/src/test/scala/millfork/test/ArraySuite.scala b/src/test/scala/millfork/test/ArraySuite.scala index 4e5ea90a..cf14a670 100644 --- a/src/test/scala/millfork/test/ArraySuite.scala +++ b/src/test/scala/millfork/test/ArraySuite.scala @@ -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) + } }