From 86ef4fcaf40eea4bd00c9876baa54fb575c39ed7 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Tue, 19 Dec 2017 18:58:33 +0100 Subject: [PATCH] Allow for preinitialized global variables --- src/main/scala/millfork/Main.scala | 2 +- src/main/scala/millfork/env/Environment.scala | 22 ++++++++++----- src/main/scala/millfork/env/Thing.scala | 26 +++++++++++++++--- .../scala/millfork/output/Assembler.scala | 22 ++++++++++++--- .../scala/millfork/test/BasicSymonTest.scala | 27 +++++++++++++++++++ src/test/scala/millfork/test/emu/EmuRun.scala | 7 ++++- 6 files changed, 91 insertions(+), 15 deletions(-) diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 13f5235c..21edf0ab 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -102,7 +102,7 @@ object Main { ErrorReporting.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B") ErrorReporting.debug(f"Optimized code size: ${assembler.optimizedCodeSize}%5d B") ErrorReporting.debug(f"Gain: ${(100L * (assembler.unoptimizedCodeSize - assembler.optimizedCodeSize) / assembler.unoptimizedCodeSize.toDouble).round}%5d%%") - ErrorReporting.debug(f"Initialized arrays: ${assembler.initializedArraysSize}%5d B") + ErrorReporting.debug(f"Initialized variables: ${assembler.initializedVariablesSize}%5d B") if (c.outputAssembly) { val path = Paths.get(assOutput) diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index c5d8cb2b..776a850a 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -54,9 +54,10 @@ class Environment(val parent: Option[Environment], val prefix: String) { case _ => None }.toList - def allPreallocatables: List[PrellocableThing] = things.values.flatMap { + def allPreallocatables: List[PreallocableThing] = things.values.flatMap { case m: NormalFunction => Some(m) case m: InitializedArray => Some(m) + case m: InitializedMemoryVariable => Some(m) case _ => None }.toList @@ -413,7 +414,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { stmt.assemblyParamPassingConvention match { case ByVariable(name) => val zp = typ.name == "pointer" // TODO - val v = MemoryVariable(prefix + name, typ, if (zp) VariableAllocationMethod.Zeropage else VariableAllocationMethod.Auto) + val v = UninitializedMemoryVariable(prefix + name, typ, if (zp) VariableAllocationMethod.Zeropage else VariableAllocationMethod.Auto) addThing(v, stmt.position) registerAddressConstant(v, stmt.position) if (typ.size == 2) { @@ -452,7 +453,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { case Some(aa) => RelativeArray(stmt.name + ".array", aa, length.toInt) } addThing(array, stmt.position) - registerAddressConstant(MemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position) + registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position) val a = address match { case None => array.toAddress case Some(aa) => aa @@ -486,7 +487,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { val data = contents.map(x => eval(x).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant contents", stmt.position))) val array = InitializedArray(stmt.name + ".array", address, data) addThing(array, stmt.position) - registerAddressConstant(MemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position) + registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position) val a = address match { case None => array.toAddress case Some(aa) => aa @@ -538,7 +539,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } } else { if (stmt.stack && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be on stack", position) - if (stmt.initialValue.isDefined) ErrorReporting.error(s"`$name` is not a constant and cannot have a value", position) + if (stmt.initialValue.isDefined && parent.isDefined) ErrorReporting.error(s"`$name` is local and not a constant and therefore cannot have a value", position) if (stmt.stack) { val v = StackVariable(prefix + name, typ, this.baseStackOffset) baseStackOffset += typ.size @@ -550,7 +551,16 @@ class Environment(val parent: Option[Environment], val prefix: String) { } else { val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({ val alloc = if (typ.name == "pointer") VariableAllocationMethod.Zeropage else if (stmt.global) VariableAllocationMethod.Static else VariableAllocationMethod.Auto - val v = MemoryVariable(prefix + name, typ, alloc) + if (alloc != VariableAllocationMethod.Static && stmt.initialValue.isDefined) { + ErrorReporting.error(s"`$name` cannot be preinitialized`", position) + } + val v = stmt.initialValue.fold[MemoryVariable](UninitializedMemoryVariable(prefix + name, typ, alloc)){ive => + if (options.flags(CompilationFlag.ReadOnlyArrays)) { + ErrorReporting.warn("Initialized variable in read-only segment", options, position) + } + val ivc = eval(ive).getOrElse(Constant.error(s"Initial value of `$name` is not a constant", position)) + InitializedMemoryVariable(name, None, typ, ivc) + } registerAddressConstant(v, stmt.position) (v, v.toAddress) })(a => { diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 38d13035..4ab982c1 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -75,7 +75,7 @@ sealed trait ThingInMemory extends Thing { def toAddress: Constant } -sealed trait PrellocableThing extends ThingInMemory { +sealed trait PreallocableThing extends ThingInMemory { def shouldGenerate: Boolean def address: Option[Constant] @@ -118,7 +118,15 @@ case class StackVariable(name: String, typ: Type, baseOffset: Int) extends Varia def sizeInBytes: Int = typ.size } -case class MemoryVariable(name: String, typ: Type, alloc: VariableAllocationMethod.Value) extends VariableInMemory with UninitializedMemory { +object MemoryVariable { + def unapply(v: MemoryVariable) = Some((v.name, v.typ, v.alloc)) +} + +abstract class MemoryVariable extends VariableInMemory { + def alloc: VariableAllocationMethod.Value +} + +case class UninitializedMemoryVariable(name: String, typ: Type, alloc: VariableAllocationMethod.Value) extends MemoryVariable with UninitializedMemory { override def sizeInBytes: Int = typ.size override def zeropage: Boolean = alloc == VariableAllocationMethod.Zeropage @@ -126,6 +134,16 @@ case class MemoryVariable(name: String, typ: Type, alloc: VariableAllocationMeth override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this) } +case class InitializedMemoryVariable(name: String, address: Option[Constant], typ: Type, initialValue: Constant) extends MemoryVariable with PreallocableThing { + override def zeropage: Boolean = false + + override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this) + + override def shouldGenerate: Boolean = true + + override def alloc: VariableAllocationMethod.Value = VariableAllocationMethod.Static +} + trait MlArray extends ThingInMemory case class UninitializedArray(name: String, sizeInBytes: Int) extends MlArray with UninitializedMemory { @@ -138,7 +156,7 @@ case class RelativeArray(name: String, address: Constant, sizeInBytes: Int) exte override def toAddress: Constant = address } -case class InitializedArray(name: String, address: Option[Constant], contents: List[Constant]) extends MlArray with PrellocableThing { +case class InitializedArray(name: String, address: Option[Constant], contents: List[Constant]) extends MlArray with PreallocableThing { override def shouldGenerate = true } @@ -196,7 +214,7 @@ case class NormalFunction(name: String, code: List[ExecutableStatement], interrupt: Boolean, reentrant: Boolean, - position: Option[Position]) extends FunctionInMemory with PrellocableThing { + position: Option[Position]) extends FunctionInMemory with PreallocableThing { override def shouldGenerate = true } diff --git a/src/main/scala/millfork/output/Assembler.scala b/src/main/scala/millfork/output/Assembler.scala index d4f06231..42183256 100644 --- a/src/main/scala/millfork/output/Assembler.scala +++ b/src/main/scala/millfork/output/Assembler.scala @@ -21,7 +21,7 @@ class Assembler(private val rootEnv: Environment) { var env = rootEnv.allThings var unoptimizedCodeSize = 0 var optimizedCodeSize = 0 - var initializedArraysSize = 0 + var initializedVariablesSize = 0 val mem = new CompiledMemory val labelMap = mutable.Map[String, Int]() @@ -154,7 +154,7 @@ class Assembler(private val rootEnv: Environment) { mem.banks(0).writeable(index) = true index += 1 } - initializedArraysSize += items.length + initializedVariablesSize += items.length case InitializedArray(name, Some(_), items) => ??? case f: NormalFunction if f.address.isDefined => var index = f.address.get.asInstanceOf[NumericConstant].value.toInt @@ -187,7 +187,23 @@ class Assembler(private val rootEnv: Environment) { mem.banks(0).writeable(index) = true index += 1 } - initializedArraysSize += items.length + initializedVariablesSize += items.length + case m@InitializedMemoryVariable(name, None, typ, value) => + if (options.flags(CompilationFlag.PreventJmpIndirectBug) && (index & 0xff) + typ.size > 0x100) { + index = (index & 0xffff00) + 0x100 + } + labelMap(name) = index + val altName = m.name.stripPrefix(env.prefix) + "`" + env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer")) + assembly.append("* = $" + index.toHexString) + assembly.append(name) + for(i <- 0 until typ.size) { + writeByte(0, index, value.subbyte(i)) + assembly.append(" !byte " + value.subbyte(i).quickSimplify) + mem.banks(0).writeable(index) = true + index += 1 + } + initializedVariablesSize += typ.size case _ => } val allocator = platform.allocator diff --git a/src/test/scala/millfork/test/BasicSymonTest.scala b/src/test/scala/millfork/test/BasicSymonTest.scala index ddf0f6a3..e34b7441 100644 --- a/src/test/scala/millfork/test/BasicSymonTest.scala +++ b/src/test/scala/millfork/test/BasicSymonTest.scala @@ -25,4 +25,31 @@ class BasicSymonTest extends FunSuite with Matchers { | } """.stripMargin).readByte(0xc000) should equal(1) } + + test("Preallocated variables") { + val m = EmuUnoptimizedRun( + """ + | array output [2] @$c000 + | byte number = 4 + | void main () { + | output[0] = number + | number += 1 + | output[1] = number + | } + """.stripMargin) + m.readByte(0xc000) should equal(4) + m.readByte(0xc001) should equal(5) + } + + test("Preallocated variables 2") { + val m = EmuUnoptimizedRun( + """ + | word output @$c000 + | word number = 344 + | void main () { + | output = number + | } + """.stripMargin) + m.readWord(0xc000) should equal(344) + } } diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index 59b0c853..19d79c82 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -6,7 +6,7 @@ import com.loomcom.symon.{Bus, Cpu, CpuState} import fastparse.core.Parsed.{Failure, Success} import millfork.assembly.opt.AssemblyOptimization import millfork.compiler.{CompilationContext, MlCompiler} -import millfork.env.{Environment, InitializedArray, NormalFunction} +import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction} import millfork.error.ErrorReporting import millfork.node.StandardCallGraph import millfork.node.opt.NodeOptimization @@ -134,6 +134,11 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], d.contents.foreach(c => println(" !byte " + c)) unoptimizedSize += d.contents.length optimizedSize += d.contents.length + case d: InitializedMemoryVariable => + println(d.name) + 0.until(d.typ.size).foreach(c => println(" !byte " + d.initialValue.subbyte(c))) + unoptimizedSize += d.typ.size + optimizedSize += d.typ.size } ErrorReporting.assertNoErrors("Compile failed")