diff --git a/.gitignore b/.gitignore index bcb9e9ec..08007ca8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ project/project/target/ stuff releases src/test/scala/experiments/ +# doesn't work yet +examples/lunix/ # hidden files *.~ @@ -18,6 +20,7 @@ src/test/scala/experiments/ # compiled Millfork files *.prg +*.seq *.asm *.lbl *.xex diff --git a/CHANGELOG.md b/CHANGELOG.md index fe396b96..30baf141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Current version -* Preliminary Atari 2600 and BBC Micro support. +* Preliminary Atari 2600, BBC Micro and LUnix support. * Added array initialization syntax with `for` (not yet finalized). diff --git a/README.md b/README.md index 511f82cb..77383db5 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ For binary releases, see: https://github.com/KarolS/millfork/releases (latest: 0 * Commodore 128 + * Commodore 64/128 running LUnix/LNG 0.21 (experimental) + * Commodore PET * Commodore Vic-20 (stock or with RAM extensions) diff --git a/docs/api/target-platforms.md b/docs/api/target-platforms.md index 438eef73..b7fb4ab2 100644 --- a/docs/api/target-platforms.md +++ b/docs/api/target-platforms.md @@ -15,6 +15,8 @@ The following platforms are currently supported: * `c64_scpu16` – Commodore 64 with SuperCPU in native, 16-bit mode (very buggy) +* `lunix` – Commodore 64 or 128 running LUnix/LNG 0.21 + * `c16` – Commodore 16 * `plus4` – Commodore Plus/4 @@ -121,7 +123,9 @@ Every platform is defined in an `.ini` file with an appropriate name. default is `false` on 65C02-compatible processors and `true` elsewhere * `compact_dispatch_params` – whether parameter values in return dispatch statements may overlap other objects, default is `true` - This may cause problems if the parameter table is stored next to a hardware register that has side effects when reading. + This may cause problems if the parameter table is stored next to a hardware register that has side effects when reading. + + * `lunix` – generate relocatable code for LUnix/LNG, default is `false` #### `[allocation]` section @@ -155,6 +159,8 @@ Default: `after_code`. * `single` – output a single file, based mostly, but not necessarily only on data in the `default` segment (the default) + * `lunix` – like `single`, but add data necessary for relocation between code and data (requires `lunix` option in the `compilation` section) + * `per_segment` – generate a separate file with each segment * `format` – output file format; a comma-separated list of tokens: @@ -163,10 +169,14 @@ Default: `after_code`. * `startaddr` – little-endian 16-bit address of the first used byte of the compiled output (not necessarily the segment start) + * `startpage` – the high byte of `startaddr` + * `endaddr` – little-endian 16-bit address of the last used byte of the compiled output (usually not the segment end) * `allocated` – all used bytes + * `pagecount` – the number of pages used by all used bytes (including partially filled pages) + * `:` - inclusive range of bytes * `::` - inclusive range of bytes in a given segment diff --git a/include/lunix.ini b/include/lunix.ini new file mode 100644 index 00000000..2c1246e4 --- /dev/null +++ b/include/lunix.ini @@ -0,0 +1,24 @@ +; Commodore 64/128 running LUnix 0.21 + +[compilation] +arch=nmos +modules=lunix +lunix=true + + +[allocation] +zp_pointers=$80-$bf +segments=default +default_code_segment=default +segment_default_start=$1006 +segment_default_codeend=$8fff +segment_default_datastart=after_code +segment_default_end=$8fff + + +[output] +style=lunix +format=$ff,$fe,0,21,pagecount,startpage,allocated +extension=prg + + diff --git a/include/lunix.mfk b/include/lunix.mfk new file mode 100644 index 00000000..165faeeb --- /dev/null +++ b/include/lunix.mfk @@ -0,0 +1,225 @@ +const word lkf_jumptab = $200 + +inline asm byte get_ipid() { + ? lda 2 + ? rts +} + +pointer tsp @4 + +pointer argv @$80 +byte argc + +inline asm byte get_pdmajor() { + ? ldy #$25 + ? lda (tsp),y + ? rts +} + +inline asm byte get_pdminor() { + ? ldy #$26 + ? lda (tsp),y + ? rts +} + +inline asm word get_pid() { + ? ldy #$28 + ? lda (tsp),y + ? tax + ? dey + ? lda (tsp),y + ? rts +} + +inline asm byte get_parent_ipid() { + ? ldy #$29 + ? lda (tsp),y + ? rts +} + +byte errno + +inline asm void set_errno_on_carry_return_a() { + ? tax + ? jmp set_errno_on_carry_return_x() +} + +inline asm void set_errno_on_carry_return_x() { + ? bcs _no_error + ? lda #0 + _no_error: + ? sta errno + ? txa + ? rts +} + +inline asm void set_errno_on_carry_return_nothing() { + ? bcs _no_error + ? lda #0 + _no_error: + ? sta errno + ? rts +} + +asm void set_zpsize(byte a) @lkf_jumptab + $0000 extern + +asm void get_moduleif() @lkf_jumptab + $0002 extern // TODO + +asm void _fopen(word ay, byte x) @lkf_jumptab + $0004 extern +asm byte fopen(word ay, byte x) { + ? jsr _fopen + ? jmp set_errno_on_carry_return_x +} + +asm void _fopendir(word ay) @lkf_jumptab + $0006 extern +asm byte fopendir(word ay) { + ? jsr _fopendir + ? jmp set_errno_on_carry_return_x +} + +asm void _fclose(byte x) @lkf_jumptab + $0008 extern +asm byte fclose(byte x) { + ? jsr _fclose + ? jmp set_errno_on_carry_return_nothing +} + +asm byte _fgetc(byte x) @lkf_jumptab + $000a extern +asm byte fgetc(byte x) { + ? jsr _fgetc + ? jmp set_errno_on_carry_return_a +} + +asm void _fputc(byte x, byte a) @lkf_jumptab + $000c extern +inline asm void fputc_blocking(byte x, byte a) { + ? sec + ? jmp _fputc +} +inline asm void fputc_nonblocking(byte x, byte a) { + ? clc + ? jmp _fputc +} + +asm void _fcmd(word ay, byte x) @lkf_jumptab + $000e extern +asm byte fcmd(word ay, byte x) { + ? jsr _fcmd + ? jmp set_errno_on_carry_return_x +} +asm void _freaddir(word ay, byte x) @lkf_jumptab + $0010 extern +asm byte freaddir(word ay, byte x) { + ? sec + ? jsr _freaddir + ? jmp set_errno_on_carry_return_a +} + +asm word _fgetdevice(byte x) @lkf_jumptab + $0012 extern +asm word fgetdevice(byte x) { + ? jsr _fgetdevice + ? txa + ? pha + ? tya + ? tax + ? pla + ? rts +} +asm void _strout(byte x) @lkf_jumptab + $0014 extern +macro asm void strout(word const string, byte x) { + ? jsr _strout + bit string + ? jsr set_errno_on_carry_return_nothing +} +asm void popen() @lkf_jumptab + $0016 extern // TODO +asm void ufd_open() @lkf_jumptab + $0018 extern // TODO +asm void _fdup(byte x) @lkf_jumptab + $001a extern +inline asm byte fdup(byte x) { + ? jsr _fdup + ? txa + ? rts +} +asm void print_error() @lkf_jumptab + $001c extern // TODO + +asm void suicerrout(byte a) @lkf_jumptab + $001e extern +asm void suicide(byte a) @lkf_jumptab + $0020 extern + +asm void _palloc(byte a) @lkf_jumptab + $0022 extern +asm byte palloc(byte a) { + ? jsr _palloc + ? jmp set_errno_on_carry_return_x +} +asm void free(byte x) @lkf_jumptab + $0024 extern +asm void force_taskswitch() @lkf_jumptab + $0026 extern + +asm void forkto() @lkf_jumptab + $0028 extern // TODO +asm void getipid() @lkf_jumptab + $002a extern // TODO +asm void signal() @lkf_jumptab + $002c extern // TODO +asm void sendsignal() @lkf_jumptab + $002e extern // TODO +asm void wait() @lkf_jumptab + $0030 extern // TODO + +asm void sleep(word xy) @lkf_jumptab + $0032 extern +asm void _lock(byte x) @lkf_jumptab + $0034 extern +inline asm void lock(byte x) { + ? clc + ? jmp _lock +} +asm void unlock(byte x) @lkf_jumptab + $0036 extern +asm void suspend(word ax) @lkf_jumptab + $0038 extern + +asm void hook_alert() @lkf_jumptab + $003a extern // TODO +asm void hook_irq() @lkf_jumptab + $003c extern // TODO +asm void hook_nmi() @lkf_jumptab + $003e extern // TODO + +asm void panic() @lkf_jumptab + $0040 extern +asm void locktsw() @lkf_jumptab + $0042 extern +asm void unlocktsw() @lkf_jumptab + $0044 extern + +asm void add_module() @lkf_jumptab + $0046 extern // TODO +asm void fix_module() @lkf_jumptab + $0048 extern // TODO +asm void mpalloc() @lkf_jumptab + $004a extern // TODO +asm void spalloc() @lkf_jumptab + $004c extern // TODO +asm void pfree() @lkf_jumptab + $004e extern // TODO +asm void mun_block() @lkf_jumptab + $0050 extern // TODO +asm void catcherr() @lkf_jumptab + $0052 extern // TODO + +asm void printk(byte a) @lkf_jumptab + $0054 extern +asm void putchar(byte a) @lkf_jumptab + $0054 extern // alias +asm void hexout(byte a) @lkf_jumptab + $0056 extern +asm void disable_nmi() @lkf_jumptab + $0058 extern +asm void enable_nmi() @lkf_jumptab + $005a extern + +asm void get_bitadr() @lkf_jumptab + $005c extern // TODO +asm void addtask() @lkf_jumptab + $005e extern // TODO +asm void get_smbptr() @lkf_jumptab + $0060 extern // TODO +asm void smb_alloc() @lkf_jumptab + $0062 extern // TODO +asm void smb_free() @lkf_jumptab + $0064 extern // TODO +asm void alloc_pfd() @lkf_jumptab + $0066 extern // TODO +asm void io_return() @lkf_jumptab + $0068 extern // TODO +asm void io_return_error() @lkf_jumptab + $006a extern // TODO +asm void ref_increment() @lkf_jumptab + $006c extern // TODO +asm void p_insert() @lkf_jumptab + $006e extern // TODO +asm void p_remove() @lkf_jumptab + $0070 extern // TODO +asm void _raw_alloc() @lkf_jumptab + $0072 extern // TODO +asm void exe_reloc() @lkf_jumptab + $0074 extern // TODO +asm void exe_test() @lkf_jumptab + $0076 extern // TODO +asm void init() @lkf_jumptab + $0078 extern // TODO +asm void keyb_joy0() @lkf_jumptab + $007a extern // TODO +asm void keyb_joy1() @lkf_jumptab + $007c extern // TODO +asm void keyb_scan() @lkf_jumptab + $007e extern // TODO +asm void keyb_stat() @lkf_jumptab + $0080 extern // TODO + +asm byte random() @lkf_jumptab + $0082 extern +asm void srandom(word ay) @lkf_jumptab + $0084 extern + +asm void getenv() @lkf_jumptab + $0086 extern // TODO +asm void setenv() @lkf_jumptab + $0088 extern // TODO +asm void loado65() @lkf_jumptab + $008a extern // TODO + +asm byte __start() @$1006 { + ? lda argv + ? sta argc + ? lda #0 + ? sta argv + lda #__zeropage_usage // TODO + jsr set_zpsize + jsr main + jsr suicide +} + diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index f1cea382..e665cabc 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -104,6 +104,9 @@ object Cpu extends Enumeration { case "6502" => Mos case "6510" => Mos case "strict" => StrictMos + case "strictnmos" => StrictMos + case "strict6502" => StrictMos + case "strict6510" => StrictMos case "cmos" => Cmos case "65sc02" => Cmos case "sc02" => Cmos @@ -135,7 +138,7 @@ object CompilationFlag extends Enumeration { // optimization options: DangerousOptimizations, InlineFunctions, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed, // memory allocation options - VariableOverlap, CompactReturnDispatchParams, + VariableOverlap, CompactReturnDispatchParams, LUnixRelocatableCode, // runtime check options CheckIndexOutOfBounds, // special options @@ -150,6 +153,7 @@ object CompilationFlag extends Enumeration { val allWarnings: Set[CompilationFlag.Value] = Set(ExtraComparisonWarnings) val fromString = Map( + "lunix" -> LUnixRelocatableCode, "emit_illegals" -> EmitIllegals, "emit_cmos" -> EmitCmosOpcodes, "emit_65ce02" -> Emit65CE02Opcodes, diff --git a/src/main/scala/millfork/Platform.scala b/src/main/scala/millfork/Platform.scala index 481dbee1..add96d72 100644 --- a/src/main/scala/millfork/Platform.scala +++ b/src/main/scala/millfork/Platform.scala @@ -13,7 +13,7 @@ import org.apache.commons.configuration2.INIConfiguration */ object OutputStyle extends Enumeration { - val Single, PerBank = Value + val Single, PerBank, LUnix = Value } class Platform( @@ -135,7 +135,7 @@ object Platform { val freePointers = as.get(classOf[String], "zp_pointers", "all") match { case "all" => List.tabulate(128)(_ * 2) - case xs => xs.split("[, ]+").map(parseNumber).toList + case xs => xs.split("[, ]+").flatMap(parseNumberOrRange).toList } val codeAllocators = banks.map(b => b -> new UpwardByteAllocator(bankStarts(b), bankCodeEnds(b))) @@ -148,7 +148,9 @@ object Platform { val os = conf.getSection("output") val outputPackager = SequenceOutput(os.get(classOf[String], "format", "").split("[, ]+").filter(_.nonEmpty).map { case "startaddr" => StartAddressOutput + case "startpage" => StartPageOutput case "endaddr" => EndAddressOutput + case "pagecount" => PageCountOutput case "allocated" => AllocatedDataOutput case n => n.split(":").filter(_.nonEmpty) match { case Array(b, s, e) => BankFragmentOutput(b, parseNumber(s), parseNumber(e)) @@ -162,6 +164,7 @@ object Platform { val outputStyle = os.get(classOf[String], "style", "single") match { case "" | "single" => OutputStyle.Single case "per_bank" | "per_segment" => OutputStyle.PerBank + case "lunix" => OutputStyle.LUnix case x => ErrorReporting.fatal(s"Invalid output style: `$x`") } @@ -175,6 +178,18 @@ object Platform { outputStyle) } + def parseNumberOrRange(s:String): Seq[Int] = { + if (s.contains("-")) { + var segments = s.split("-") + if (segments.length != 2) { + ErrorReporting.fatal(s"Invalid range: `$s`") + } + Range(parseNumber(segments(0)), parseNumber(segments(1)), 2) + } else { + Seq(parseNumber(s)) + } + } + def parseNumber(s: String): Int = { if (s.startsWith("$")) { Integer.parseInt(s.substring(1), 16) diff --git a/src/main/scala/millfork/env/Constant.scala b/src/main/scala/millfork/env/Constant.scala index bdc4fa56..c7b52573 100644 --- a/src/main/scala/millfork/env/Constant.scala +++ b/src/main/scala/millfork/env/Constant.scala @@ -70,7 +70,7 @@ sealed trait Constant { def quickSimplify: Constant = this - def isRelatedTo(v: Variable): Boolean + def isRelatedTo(v: Thing): Boolean } case class AssertByte(c: Constant) extends Constant { @@ -78,13 +78,15 @@ case class AssertByte(c: Constant) extends Constant { override def requiredSize: Int = 1 - override def isRelatedTo(v: Variable): Boolean = c.isRelatedTo(v) + override def isRelatedTo(v: Thing): Boolean = c.isRelatedTo(v) override def quickSimplify: Constant = AssertByte(c.quickSimplify) } case class UnexpandedConstant(name: String, requiredSize: Int) extends Constant { - override def isRelatedTo(v: Variable): Boolean = false + override def isRelatedTo(v: Thing): Boolean = false + + override def toString: String = name } case class NumericConstant(value: Long, requiredSize: Int) extends Constant { @@ -111,7 +113,7 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant { override def toString: String = if (value > 9) value.formatted("$%X") else value.toString - override def isRelatedTo(v: Variable): Boolean = false + override def isRelatedTo(v: Thing): Boolean = false } case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant { @@ -119,7 +121,7 @@ case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant { override def toString: String = thing.name - override def isRelatedTo(v: Variable): Boolean = thing.name == v.name + override def isRelatedTo(v: Thing): Boolean = thing.name == v.name } case class SubbyteConstant(base: Constant, index: Int) extends Constant { @@ -144,7 +146,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant { case 3 => ".b3" }) - override def isRelatedTo(v: Variable): Boolean = base.isRelatedTo(v) + override def isRelatedTo(v: Thing): Boolean = base.isRelatedTo(v) } object MathOperator extends Enumeration { @@ -304,5 +306,5 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co override def requiredSize: Int = lhs.requiredSize max rhs.requiredSize - override def isRelatedTo(v: Variable): Boolean = lhs.isRelatedTo(v) || rhs.isRelatedTo(v) + override def isRelatedTo(v: Thing): Boolean = lhs.isRelatedTo(v) || rhs.isRelatedTo(v) } \ No newline at end of file diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 6ec4aa70..6fcd76cf 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -100,7 +100,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } }.toSet val toAdd = things.values.flatMap { - case m: UninitializedMemory if nf.isDefined == isLocalVariableName(m.name) => + case m: UninitializedMemory if nf.isDefined == isLocalVariableName(m.name) && !m.name.endsWith(".addr") && maybeGet[Thing](m.name + ".array").isEmpty => val vertex = if (options.flag(CompilationFlag.VariableOverlap)) { nf.fold[VariableVertex](GlobalVertex) { f => if (m.alloc == VariableAllocationMethod.Static) { @@ -240,20 +240,23 @@ class Environment(val parent: Option[Environment], val prefix: String) { if (parent.isEmpty) { addThing(VoidType, None) addThing(BuiltInBooleanType, None) - addThing(BasicPlainType("byte", 1), None) - addThing(BasicPlainType("word", 2), None) + val b = BasicPlainType("byte", 1) + val w = BasicPlainType("word", 2) + addThing(b, None) + addThing(w, None) addThing(BasicPlainType("farword", 3), None) addThing(BasicPlainType("long", 4), None) - addThing(DerivedPlainType("pointer", get[PlainType]("word"), isSigned = false), None) -// addThing(DerivedPlainType("farpointer", get[PlainType]("farword"), isSigned = false), None) - addThing(DerivedPlainType("ubyte", get[PlainType]("byte"), isSigned = false), None) - addThing(DerivedPlainType("sbyte", get[PlainType]("byte"), isSigned = true), None) + addThing(DerivedPlainType("pointer", w, isSigned = false), None) + // addThing(DerivedPlainType("farpointer", get[PlainType]("farword"), isSigned = false), None) + addThing(DerivedPlainType("ubyte", b, isSigned = false), None) + addThing(DerivedPlainType("sbyte", b, isSigned = true), None) val trueType = ConstantBooleanType("true$", value = true) val falseType = ConstantBooleanType("false$", value = false) addThing(trueType, None) addThing(falseType, None) addThing(ConstantThing("true", NumericConstant(0, 0), trueType), None) addThing(ConstantThing("false", NumericConstant(0, 0), falseType), None) + addThing(ConstantThing("__zeropage_usage", UnexpandedConstant("__zeropage_usage", 1), b), None) addThing(FlagBooleanType("set_carry", Opcode.BCS, Opcode.BCC), None) addThing(FlagBooleanType("clear_carry", Opcode.BCC, Opcode.BCS), None) addThing(FlagBooleanType("set_overflow", Opcode.BVS, Opcode.BVC), None) @@ -502,7 +505,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } val env = new Environment(Some(this), name + "$") - stmt.params.foreach(p => env.registerParameter(p)) + stmt.params.foreach(p => env.registerParameter(p, options)) val params = if (stmt.assembly) { AssemblyParamSignature(stmt.params.map { pd => @@ -539,7 +542,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { stmt.bank ) addThing(mangled, stmt.position) - registerAddressConstant(mangled, stmt.position) + registerAddressConstant(mangled, stmt.position, options) addThing(ConstantThing(name + '`', addr, w), stmt.position) } @@ -585,19 +588,29 @@ class Environment(val parent: Option[Environment], val prefix: String) { declaredBank = stmt.bank ) addThing(mangled, stmt.position) - registerAddressConstant(mangled, stmt.position) + registerAddressConstant(mangled, stmt.position, options) } } } - private def registerAddressConstant(thing: ThingInMemory, position: Option[Position]): Unit = { - val addr = thing.toAddress - addThing(ConstantThing(thing.name + ".addr", addr, get[Type]("pointer")), position) - addThing(ConstantThing(thing.name + ".addr.hi", addr.hiByte, get[Type]("byte")), position) - addThing(ConstantThing(thing.name + ".addr.lo", addr.loByte, get[Type]("byte")), position) + private def registerAddressConstant(thing: ThingInMemory, position: Option[Position], options: CompilationOptions): Unit = { + if (!thing.zeropage && options.flag(CompilationFlag.LUnixRelocatableCode)) { + val b = get[Type]("byte") + val w = get[Type]("word") + val relocatable = UninitializedMemoryVariable(thing.name + ".addr", w, VariableAllocationMethod.Static, None) + val addr = relocatable.toAddress + addThing(relocatable, position) + addThing(RelativeVariable(thing.name + ".addr.hi", addr + 1, b, zeropage = false, None), position) + addThing(RelativeVariable(thing.name + ".addr.lo", addr, b, zeropage = false, None), position) + } else { + val addr = thing.toAddress + addThing(ConstantThing(thing.name + ".addr", addr, get[Type]("pointer")), position) + addThing(ConstantThing(thing.name + ".addr.hi", addr.hiByte, get[Type]("byte")), position) + addThing(ConstantThing(thing.name + ".addr.lo", addr.loByte, get[Type]("byte")), position) + } } - def registerParameter(stmt: ParameterDeclaration): Unit = { + def registerParameter(stmt: ParameterDeclaration, options: CompilationOptions): Unit = { val typ = get[Type](stmt.typ) val b = get[Type]("byte") val w = get[Type]("word") @@ -607,7 +620,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { val zp = typ.name == "pointer" // TODO val v = UninitializedMemoryVariable(prefix + name, typ, if (zp) VariableAllocationMethod.Zeropage else VariableAllocationMethod.Auto, None) addThing(v, stmt.position) - registerAddressConstant(v, stmt.position) + registerAddressConstant(v, stmt.position, options) val addr = v.toAddress typ.size match { case 2 => @@ -674,7 +687,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } } - def registerArray(stmt: ArrayDeclarationStatement): Unit = { + def registerArray(stmt: ArrayDeclarationStatement, options: CompilationOptions): Unit = { val b = get[Type]("byte") val p = get[Type]("pointer") stmt.elements match { @@ -694,18 +707,30 @@ class Environment(val parent: Option[Environment], val prefix: String) { declaredBank = stmt.bank) } addThing(array, stmt.position) - registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, stmt.bank), stmt.position) + registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, stmt.bank), stmt.position, options) val a = address match { case None => array.toAddress case Some(aa) => aa } addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false, declaredBank = stmt.bank), stmt.position) - addThing(ConstantThing(stmt.name, a, p), stmt.position) - addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.lo", a.loByte.quickSimplify, b), stmt.position) + if (options.flag(CompilationFlag.LUnixRelocatableCode)) { + val b = get[Type]("byte") + val w = get[Type]("word") + val relocatable = UninitializedMemoryVariable(stmt.name, w, VariableAllocationMethod.Static, None) + val addr = relocatable.toAddress + addThing(relocatable, stmt.position) + addThing(RelativeVariable(stmt.name + ".addr.hi", addr + 1, b, zeropage = false, None), stmt.position) + addThing(RelativeVariable(stmt.name + ".addr.lo", addr, b, zeropage = false, None), stmt.position) + addThing(RelativeVariable(stmt.name + ".array.hi", addr + 1, b, zeropage = false, None), stmt.position) + addThing(RelativeVariable(stmt.name + ".array.lo", addr, b, zeropage = false, None), stmt.position) + } else { + addThing(ConstantThing(stmt.name, a, p), stmt.position) + addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(stmt.name + ".array.lo", a.loByte.quickSimplify, b), stmt.position) + } if (length < 256) { addThing(ConstantThing(stmt.name + ".length", lengthConst, b), stmt.position) } @@ -730,18 +755,28 @@ class Environment(val parent: Option[Environment], val prefix: String) { val array = InitializedArray(stmt.name + ".array", address, contents, declaredBank = stmt.bank) addThing(array, stmt.position) registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, - declaredBank = stmt.bank), stmt.position) + declaredBank = stmt.bank), stmt.position, options) val a = address match { case None => array.toAddress case Some(aa) => aa } addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false, declaredBank = stmt.bank), stmt.position) - addThing(ConstantThing(stmt.name, a, p), stmt.position) - addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.lo", a.loByte.quickSimplify, b), stmt.position) + if (options.flag(CompilationFlag.LUnixRelocatableCode)) { + val b = get[Type]("byte") + val w = get[Type]("word") + val relocatable = UninitializedMemoryVariable(stmt.name, w, VariableAllocationMethod.Static, None) + val addr = relocatable.toAddress + addThing(relocatable, stmt.position) + addThing(RelativeVariable(stmt.name + ".array.hi", addr + 1, b, zeropage = false, None), stmt.position) + addThing(RelativeVariable(stmt.name + ".array.lo", addr, b, zeropage = false, None), stmt.position) + } else { + addThing(ConstantThing(stmt.name, a, p), stmt.position) + addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(stmt.name + ".array.lo", a.loByte.quickSimplify, b), stmt.position) + } if (length < 256) { addThing(ConstantThing(stmt.name + ".length", NumericConstant(length, 1), b), stmt.position) } @@ -846,7 +881,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } InitializedMemoryVariable(name, None, typ, ive, declaredBank = stmt.bank) } - registerAddressConstant(v, stmt.position) + registerAddressConstant(v, stmt.position, options) (v, v.toAddress) })(a => { val addr = eval(a).getOrElse(Constant.error(s"Address of `$name` has a non-constant value", position)) @@ -856,7 +891,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } val v = RelativeVariable(prefix + name, addr, typ, zeropage = zp, declaredBank = stmt.bank) - registerAddressConstant(v, stmt.position) + registerAddressConstant(v, stmt.position, options) (v, addr) }) addThing(v, stmt.position) @@ -927,7 +962,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { program.declarations.foreach { case f: FunctionDeclarationStatement => registerFunction(f, options) case v: VariableDeclarationStatement => registerVariable(v, options) - case a: ArrayDeclarationStatement => registerArray(a) + case a: ArrayDeclarationStatement => registerArray(a, options) case i: ImportStatement => () } if (options.flag(CompilationFlag.ZeropagePseudoregister) && !things.contains("__reg")) { diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 2f561489..4fe10a01 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -78,6 +78,8 @@ sealed trait TypedThing extends Thing { sealed trait ThingInMemory extends Thing { + def zeropage: Boolean + def toAddress: Constant var farFlag: Option[Boolean] = None @@ -105,12 +107,13 @@ case class Label(name: String) extends ThingInMemory { declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank) override val declaredBank: Option[String] = None + + override def zeropage: Boolean = false } sealed trait Variable extends TypedThing with VariableLikeThing sealed trait VariableInMemory extends Variable with ThingInMemory with IndexableThing { - def zeropage: Boolean override def isFar(compilationOptions: CompilationOptions): Boolean = !zeropage && farFlag.getOrElse(false) @@ -175,6 +178,8 @@ case class UninitializedArray(name: String, sizeInBytes: Int, declaredBank: Opti override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false) override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse("default") + + override def zeropage: Boolean = false } case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, declaredBank: Option[String]) extends MfArray { @@ -183,6 +188,8 @@ case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, decl override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false) override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse("default") + + override def zeropage: Boolean = false } case class InitializedArray(name: String, address: Option[Constant], contents: List[Expression], declaredBank: Option[String]) extends MfArray with PreallocableThing { @@ -191,6 +198,8 @@ case class InitializedArray(name: String, address: Option[Constant], contents: L override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false) override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank) + + override def zeropage: Boolean = false } case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean, declaredBank: Option[String]) extends VariableInMemory { @@ -242,6 +251,8 @@ case class ExternFunction(name: String, override def toAddress: Constant = address override def interrupt = false + + override def zeropage: Boolean = false } case class NormalFunction(name: String, @@ -257,6 +268,8 @@ case class NormalFunction(name: String, position: Option[Position], declaredBank: Option[String]) extends FunctionInMemory with PreallocableThing { override def shouldGenerate = true + + override def zeropage: Boolean = false } case class ConstantThing(name: String, value: Constant, typ: Type) extends TypedThing with VariableLikeThing with IndexableThing { diff --git a/src/main/scala/millfork/output/Assembler.scala b/src/main/scala/millfork/output/Assembler.scala index 89bb1b3a..1b59cc87 100644 --- a/src/main/scala/millfork/output/Assembler.scala +++ b/src/main/scala/millfork/output/Assembler.scala @@ -78,7 +78,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment, try { if (labelMap.contains(th.name)) return labelMap(th.name) if (labelMap.contains(th.name + "`")) return labelMap(th.name) - if (labelMap.contains(th.name + ".addr")) return labelMap(th.name) + if (labelMap.contains(th.name + ".addr")) return labelMap.getOrElse[Int](th.name, labelMap(th.name + ".array")) val x1 = env.maybeGet[ConstantThing](th.name).map(_.value) val x2 = env.maybeGet[ConstantThing](th.name + "`").map(_.value) val x3 = env.maybeGet[NormalFunction](th.name).flatMap(_.address) @@ -97,6 +97,9 @@ class Assembler(private val program: Program, private val rootEnv: Environment, case e: StackOverflowError => ErrorReporting.fatal("Stack overflow " + c) } + case UnexpandedConstant(name, _) => + if (labelMap.contains(name)) labelMap(name) + else ??? case SubbyteConstant(cc, i) => deepConstResolve(cc).>>>(i * 8).&(0xff) case CompoundConstant(operator, lc, rc) => val l = deepConstResolve(lc) @@ -282,6 +285,42 @@ class Assembler(private val program: Program, private val rootEnv: Environment, } case _ => } + if (options.flag(CompilationFlag.LUnixRelocatableCode)) { + env.allThings.things.foreach { + case (_, m@UninitializedMemoryVariable(name, typ, _, _)) if name.endsWith(".addr") || env.maybeGet[Thing](name + ".array").isDefined => + val isUsed = compiledFunctions.values.exists(_.exists(_.parameter.isRelatedTo(m))) +// println(m.name -> isUsed) + if (isUsed) { + val bank = m.bank(options) + if (bank != "default") ??? + val bank0 = mem.banks(bank) + var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size + 1, initialized = true, writeable = false) + labelMap(name) = index + 1 + val altName = m.name.stripPrefix(env.prefix) + "`" + val thing = if (name.endsWith(".addr")) env.get[ThingInMemory](name.stripSuffix(".addr")) else env.get[ThingInMemory](name + ".array") + env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer")) + assembly.append("* = $" + index.toHexString) + assembly.append(" !byte $2c") + assembly.append(name) + val c = thing.toAddress + writeByte(bank, index, 0x2c.toByte) // BIT abs + index += 1 + for (i <- 0 until typ.size) { + writeByte(bank, index, c.subbyte(i)) + assembly.append(" !byte " + c.subbyte(i).quickSimplify) + index += 1 + } + initializedVariablesSize += typ.size + justAfterCode += bank -> index + } + case _ => () + } + val index = codeAllocators("default").allocateBytes(mem.banks("default"), options, 1, initialized = true, writeable = false) + writeByte("default", index, 2.toByte) // BIT abs + assembly.append("* = $" + index.toHexString) + assembly.append(" !byte 2 ;; end of LUnix relocatable segment") + justAfterCode += "default" -> (index + 1) + } env.allPreallocatables.foreach { case thing@InitializedArray(name, None, items, _) => val bank = thing.bank(options) @@ -305,7 +344,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment, } initializedVariablesSize += items.length justAfterCode += bank -> index - case m@InitializedMemoryVariable(name, None, typ, value, _) => + case m@InitializedMemoryVariable(name, None, typ, value, _) => val bank = m.bank(options) val bank0 = mem.banks(bank) var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size, initialized = true, writeable = true) @@ -335,9 +374,15 @@ class Assembler(private val program: Program, private val rootEnv: Environment, for(i <- 0 until size) bank0.occupied(addr + i) = true } val variableAllocators = platform.variableAllocators - variableAllocators.foreach{case (b,a) => a.notifyAboutEndOfCode(justAfterCode(b))} + variableAllocators.foreach { case (b, a) => a.notifyAboutEndOfCode(justAfterCode(b)) } env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put) + val zeropageOccupation = mem.banks("default").occupied.slice(variableAllocators("default").pointers.head, variableAllocators("default").pointers.last + 2) + labelMap += "__zeropage_usage" -> (zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1) + labelMap += "__zeropage_first" -> (zeropageOccupation.indexOf(true) max 0) + labelMap += "__zeropage_last" -> (zeropageOccupation.lastIndexOf(true) max 0) + labelMap += "__zeropage_end" -> (zeropageOccupation.lastIndexOf(true) + 1) + env = rootEnv.allThings for ((bank, addr, b) <- bytesToWriteLater) { @@ -367,7 +412,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment, // TODO: val code = (platform.outputStyle match { - case OutputStyle.Single => List("default") + case OutputStyle.Single | OutputStyle.LUnix => List("default") case OutputStyle.PerBank => platform.bankNumbers.keys.toList }).map(b => b -> platform.outputPackager.packageOutput(mem, b)).toMap AssemblerOutput(code, assembly.toArray, labelMap.toList) diff --git a/src/main/scala/millfork/output/OutputPackager.scala b/src/main/scala/millfork/output/OutputPackager.scala index f8c985f1..d1ad69b7 100644 --- a/src/main/scala/millfork/output/OutputPackager.scala +++ b/src/main/scala/millfork/output/OutputPackager.scala @@ -45,6 +45,13 @@ object StartAddressOutput extends OutputPackager { } } +object StartPageOutput extends OutputPackager { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { + val b = mem.banks(bank) + Array(b.start.>>(8).toByte) + } +} + object EndAddressOutput extends OutputPackager { def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(bank) @@ -52,6 +59,14 @@ object EndAddressOutput extends OutputPackager { } } +object PageCountOutput extends OutputPackager { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { + val e = mem.banks(bank).end.>>(8) + val s = mem.banks(bank).start.>>(8) + Array((e - s + 1).toByte) + } +} + object AllocatedDataOutput extends OutputPackager { def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(bank) diff --git a/src/main/scala/millfork/output/VariableAllocator.scala b/src/main/scala/millfork/output/VariableAllocator.scala index 11d87f50..23aa4b5c 100644 --- a/src/main/scala/millfork/output/VariableAllocator.scala +++ b/src/main/scala/millfork/output/VariableAllocator.scala @@ -50,7 +50,7 @@ class AfterCodeByteAllocator(val endBefore: Int) extends ByteAllocator { def notifyAboutEndOfCode(org: Int): Unit = startAt = org } -class VariableAllocator(private val pointers: List[Int], private val bytes: ByteAllocator) { +class VariableAllocator(val pointers: List[Int], private val bytes: ByteAllocator) { private val pointerMap = mutable.Map[Int, Set[VariableVertex]]() private val variableMap = mutable.Map[Int, mutable.Map[Int, Set[VariableVertex]]]()