diff --git a/codeCore/src/prog8/code/target/Amiga1200Target.kt b/codeCore/src/prog8/code/target/Amiga1200Target.kt index 7fddf5e63..f5d1e2b58 100644 --- a/codeCore/src/prog8/code/target/Amiga1200Target.kt +++ b/codeCore/src/prog8/code/target/Amiga1200Target.kt @@ -2,6 +2,7 @@ package prog8.code.target import prog8.code.core.* import prog8.code.target.encodings.Encoder +import prog8.code.target.zp.M68kZeropage import java.nio.file.Path @@ -88,20 +89,6 @@ class Amiga1200Target: ICompilationTarget, address in 0xe80000u..0xefffffu // Zorro II autoconfig space override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { - zeropage = Amiga1200Zeropage(compilerOptions) + zeropage = M68kZeropage(compilerOptions) } } - - -private class Amiga1200Zeropage(options: CompilationOptions): Zeropage(options) { - override val SCRATCH_B1: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_REG: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_W1: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_W2: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_PTR: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") -} diff --git a/codeCore/src/prog8/code/target/Amiga500Target.kt b/codeCore/src/prog8/code/target/Amiga500Target.kt index e1380310d..bbb011f6d 100644 --- a/codeCore/src/prog8/code/target/Amiga500Target.kt +++ b/codeCore/src/prog8/code/target/Amiga500Target.kt @@ -2,6 +2,7 @@ package prog8.code.target import prog8.code.core.* import prog8.code.target.encodings.Encoder +import prog8.code.target.zp.M68kZeropage import java.nio.file.Path @@ -128,20 +129,6 @@ class Amiga500Target: ICompilationTarget, address in 0xe80000u..0xefffffu // Zorro II autoconfig space override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { - zeropage = AmigaZeropage(compilerOptions) + zeropage = M68kZeropage(compilerOptions) } } - - -private class AmigaZeropage(options: CompilationOptions): Zeropage(options) { - override val SCRATCH_B1: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_REG: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_W1: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_W2: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_PTR: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") -} diff --git a/codeCore/src/prog8/code/target/ConfigFileTarget.kt b/codeCore/src/prog8/code/target/ConfigFileTarget.kt index f7c0c6b8b..460ba5650 100644 --- a/codeCore/src/prog8/code/target/ConfigFileTarget.kt +++ b/codeCore/src/prog8/code/target/ConfigFileTarget.kt @@ -4,6 +4,7 @@ import prog8.code.core.* import prog8.code.source.ImportFileSystem.expandTilde import prog8.code.target.encodings.Encoder import prog8.code.target.zp.ConfigurableZeropage +import prog8.code.target.zp.M68kZeropage import java.io.IOException import java.nio.file.Path import java.util.* @@ -109,9 +110,12 @@ class ConfigFileTarget( CpuType.valueOf("CPU$cpuString") } val ioAddresses = parseAddressRanges("io_regions", props) - val zpFullsafe = parseAddressRanges("zp_fullsafe", props) - val zpKernalsafe = parseAddressRanges("zp_kernalsafe", props) - val zpBasicsafe = parseAddressRanges("zp_basicsafe", props) + // The m68k has no zero page, so the zeropage settings below don't apply to an m68k + // target and are not required in its config file. + fun zpSetting(name: String) = if(cpuType.is68k) 0u else props.getInteger(name) + val zpFullsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_fullsafe", props) + val zpKernalsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_kernalsafe", props) + val zpBasicsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_basicsafe", props) val libraryPath = expandTilde(Path(props.getString("library"))) if(!libraryPath.isDirectory()) @@ -153,12 +157,12 @@ class ConfigFileTarget( if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "), varsAddress, ioAddresses, - props.getInteger("zp_scratch_b1"), - props.getInteger("zp_scratch_reg"), - props.getInteger("zp_scratch_w1"), - props.getInteger("zp_scratch_w2"), - props.getInteger("zp_scratch_ptr"), - props.getInteger("virtual_registers"), + zpSetting("zp_scratch_b1"), + zpSetting("zp_scratch_reg"), + zpSetting("zp_scratch_w1"), + zpSetting("zp_scratch_w2"), + zpSetting("zp_scratch_ptr"), + zpSetting("virtual_registers"), zpFullsafe, zpKernalsafe, zpBasicsafe, @@ -185,7 +189,8 @@ class ConfigFileTarget( override fun isIOAddress(address: UInt): Boolean = ioAddresses.any { address in it } override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { - zeropage = ConfigurableZeropage( + // the m68k has no zero page, so the zp scratch addresses in the config file don't apply there + zeropage = if(cpu.is68k) M68kZeropage(compilerOptions) else ConfigurableZeropage( zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2, zpScratchPtr, virtualregistersStart, zpBasicsafe, diff --git a/codeCore/src/prog8/code/target/Qemu68kTarget.kt b/codeCore/src/prog8/code/target/Qemu68kTarget.kt index 929ca735f..b5ee655fd 100644 --- a/codeCore/src/prog8/code/target/Qemu68kTarget.kt +++ b/codeCore/src/prog8/code/target/Qemu68kTarget.kt @@ -2,6 +2,7 @@ package prog8.code.target import prog8.code.core.* import prog8.code.target.encodings.Encoder +import prog8.code.target.zp.M68kZeropage import java.nio.file.Path /** @@ -110,16 +111,3 @@ class Qemu68kTarget: ICompilationTarget, } } - -private class M68kZeropage(options: CompilationOptions): Zeropage(options) { - override val SCRATCH_B1: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_REG: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_W1: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_W2: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") - override val SCRATCH_PTR: UInt - get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") -} diff --git a/codeCore/src/prog8/code/target/zp/M68kZeropage.kt b/codeCore/src/prog8/code/target/zp/M68kZeropage.kt new file mode 100644 index 000000000..8c0283532 --- /dev/null +++ b/codeCore/src/prog8/code/target/zp/M68kZeropage.kt @@ -0,0 +1,21 @@ +package prog8.code.target.zp + +import prog8.code.core.CompilationOptions +import prog8.code.core.Zeropage + +/** + * The m68k has no zero page, so there are no scratch locations to hand out. + * Any attempt to use one is a bug in the code generator. + */ +class M68kZeropage(options: CompilationOptions): Zeropage(options) { + override val SCRATCH_B1: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_REG: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_W1: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_W2: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") + override val SCRATCH_PTR: UInt + get() = throw IllegalStateException("m68k shouldn't use this zeropage variable") +} diff --git a/codeGenM68k/docs/linking.md b/codeGenM68k/docs/linking.md index 9e96be985..15e2a5aaa 100644 --- a/codeGenM68k/docs/linking.md +++ b/codeGenM68k/docs/linking.md @@ -53,6 +53,16 @@ Passes the linker script (see below). ## The linker script (`link.ld`) +The compiler resolves the linker script for a target in this order: + +1. `link.ld` in the target's library directory (the `library` setting). This is how a custom + target, defined outside of the compiler, supplies its own memory layout. +2. `/prog8lib//link.ld`, for targets built into the compiler. +3. `/prog8lib/qemu68k/link.ld` as the fallback, which is the script shown below. + +Only the `ELF` output type links at all. The `AMIGAHUNK` output type (used by the built-in +Amiga targets) assembles straight to a hunk executable and ignores the linker script. + ``` ENTRY(_start) SECTIONS diff --git a/codeGenM68k/src/prog8/codegen/m68k/AssemblyProgramM68k.kt b/codeGenM68k/src/prog8/codegen/m68k/AssemblyProgramM68k.kt index 31f8dbe7b..f621faa88 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/AssemblyProgramM68k.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/AssemblyProgramM68k.kt @@ -1,14 +1,13 @@ package prog8.codegen.m68k import prog8.code.assembly.IAssemblyProgram -import prog8.code.core.CompilationOptions -import prog8.code.core.CpuType -import prog8.code.core.IErrorReporter -import prog8.code.core.OutputType +import prog8.code.core.* import prog8.code.target.Amiga1200Target import prog8.code.target.Amiga500Target +import prog8.code.target.Qemu68kTarget import java.nio.file.Files import java.nio.file.Path +import kotlin.io.path.readText class AssemblyProgramM68k(override val name: String, private val outputDir: Path) : IAssemblyProgram { @@ -20,6 +19,20 @@ class AssemblyProgramM68k(override val name: String, private val outputDir: Path fun elfFile(): Path = outputDir.resolve("$name.elf") + /** + * A target supplies its own memory layout as a "link.ld" file. A target defined by a + * config file (i.e. living outside of the compiler) keeps it in its library directory, so + * that is looked at first. Targets built into the compiler are looked up as a + * /prog8lib//link.ld resource. Anything else falls back to the qemu68k layout. + */ + private fun resolveLinkerScript(target: ICompilationTarget): String { + target.libraryPath?.resolve("link.ld")?.takeIf { Files.isReadable(it) }?.let { return it.readText() } + val targetResource = "/prog8lib/${target.name}/link.ld" + AssemblyProgramM68k::class.java.getResource(targetResource)?.let { return it.readText() } + return AssemblyProgramM68k::class.java.getResource("/prog8lib/${Qemu68kTarget.NAME}/link.ld")?.readText() + ?: error("cannot find $targetResource resource, and no qemu68k fallback either") + } + private fun runProcess(command: List, quiet: Boolean, tool: String? = null): Boolean { val proc = ProcessBuilder(command).redirectErrorStream(true) if (quiet) @@ -85,9 +98,7 @@ class AssemblyProgramM68k(override val name: String, private val outputDir: Path // Step 2: write linker script and link to ELF executable val linkScript = outputDir.resolve("$name.link.ld") val elfFile = elfFile() - val resourceUrl = AssemblyProgramM68k::class.java.getResource("/prog8lib/qemu68k/link.ld") - ?: error("cannot find /prog8lib/qemu68k/link.ld resource") - Files.writeString(linkScript, resourceUrl.readText()) + Files.writeString(linkScript, resolveLinkerScript(options.compTarget)) val linkCmd = listOf( "vlink", diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 887dcf6ae..733a00044 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -13,10 +13,12 @@ import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.choice import com.github.ajalt.clikt.parameters.types.int import prog8.ast.AstException -import prog8.code.core.toHex import prog8.code.source.ImportFileSystem import prog8.code.source.ImportFileSystem.expandTilde -import prog8.code.target.* +import prog8.code.target.CompilationTargets +import prog8.code.target.Cx16Target +import prog8.code.target.VMTarget +import prog8.code.target.getCompilationTargetByName import prog8.compiler.* import prog8.intermediate.IRFileReader import java.io.* @@ -202,11 +204,6 @@ private fun compileMain(args: Array): Boolean { presenter.printErrorLine("Invalid -varsaddress value: $varsAddressStr") return false } - val maxCliAddress = if(compilationTarget in setOf(Amiga500Target.NAME, Amiga1200Target.NAME, Qemu68kTarget.NAME, VMTarget.NAME)) 0xFFFFFFFFu else 0xFFFFu - if(varsAddress!=null && varsAddress > maxCliAddress) { - presenter.printErrorLine("vars address must be valid integer 0..${maxCliAddress.toHex()}") - return false - } if(varsGolden==true && varsHighBank!=null) { presenter.printErrorLine("Either use -varsgolden or -varshigh, not both.") @@ -217,12 +214,9 @@ private fun compileMain(args: Array): Boolean { return false } - if(compilationTarget in setOf(Amiga500Target.NAME, Amiga1200Target.NAME, Qemu68kTarget.NAME)) { - if(varsGolden || varsHighBank!=null || varsAddress!=null) { - presenter.printErrorLine("The -varsgolden/-varshigh/-varsaddress options are not available on the m68k target") - return false - } - } + // NOTE: whether the target supports these options, and the valid address range, are both + // checked in the compiler where the compilation target is resolved: it knows the actual + // CpuType and pointer size, which also covers custom targets defined by a config file. if(startVm==true) { runVm(moduleFiles.first(), quietAll==true, vmTrace==true) diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index d4d9d01b1..eeeef034d 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -777,9 +777,7 @@ private fun createAssemblyAndAssemble(program: PtProgram, else prog8.codegen.cpu6502.AsmGen6502(lastGeneratedLabelSequenceNr+1, asm6502CallIds) } - compilerOptions.compTarget.name == Qemu68kTarget.NAME -> prog8.codegen.m68k.M68kCodeGenerator(retainSSAforIR) - compilerOptions.compTarget.name == Amiga500Target.NAME -> prog8.codegen.m68k.M68kCodeGenerator(retainSSAforIR) - compilerOptions.compTarget.name == Amiga1200Target.NAME -> prog8.codegen.m68k.M68kCodeGenerator(retainSSAforIR) + compilerOptions.compTarget.cpu.is68k -> prog8.codegen.m68k.M68kCodeGenerator(retainSSAforIR) compilerOptions.compTarget.name == VMTarget.NAME -> VmCodeGen(retainSSAforIR, irCallIds) else -> throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}") } diff --git a/docs/source/targetsystem.rst b/docs/source/targetsystem.rst index 4d9ee2546..4058f3b9a 100644 --- a/docs/source/targetsystem.rst +++ b/docs/source/targetsystem.rst @@ -79,6 +79,98 @@ and ``none`` (no launcher). If omitted, the default is ``basic``. The optional ``pointer_size`` property sets the size of pointers in bytes. It defaults to ``2`` for compatibility with existing configuration files. The supported values are ``2`` and ``4``. +.. _custom_target_m68k: + +Custom targets for the 68000 CPU +-------------------------------- + +.. index:: single: Custom Targets; 68000 + +The compiler can also generate m68k code for a custom target machine, not just for the built-in +``amiga500``, ``amiga1200`` and ``qemu68k`` targets. The code generator is selected from the ``cpu`` +property rather than from the target name, so declaring an m68k CPU is all that is needed to +route a custom target to the m68k backend. + +Set the ``cpu`` property to the name of the CpuType enum value, ``M68000`` or ``M68020``:: + + cpu = M68020 + +The exact spelling matters, because the value is the name of the enum value: write ``M68020`` and +not ``68020``. The 6502 CPUs are spelled ``6502``, ``65C02`` and ``virtual``, and those are tried +both with and without a ``CPU`` prefix, so ``6502`` and ``CPU6502`` both work. + +An m68k target needs a much smaller configuration file than a 6502 one, because the m68k has no +zero page and uses real CPU registers instead of the compiler's virtual registers. This means the +zeropage related properties are not applicable, and you can simply leave them out of the file: + +* ``zp_scratch_ptr``, ``zp_scratch_b1``, ``zp_scratch_reg``, ``zp_scratch_w1``, ``zp_scratch_w2`` +* ``zp_fullsafe``, ``zp_kernalsafe``, ``zp_basicsafe`` +* ``virtual_registers`` + +They are only ignored for an m68k CPU; a 6502 target still requires them, and a typo there is still +reported as a missing property. Set ``pointer_size = 4``, since m68k pointers are 32 bit. + +Use ``output_type = ELF``. This is the only output type that links, and therefore the only one a +custom m68k target can use; the ``AMIGAHUNK`` output type assembles straight to an Amiga hunk +executable and is only available for the built-in Amiga targets. Two other things are worth knowing +when targeting bare metal: the FPU is enabled for the assembler regardless of the target, and the +assembler CPU is only upgraded to 68020 for ``amiga500`` with floating point enabled. + +.. _custom_target_m68k_linker: + +The linker script +^^^^^^^^^^^^^^^^^ + +An ``ELF`` output is produced in two steps: the compiler assembles to an object file, and then +links it into an ELF executable. The layout of that executable comes from a linker script, which +the compiler looks up in this order: + +#. ``link.ld`` in the target's library directory (the directory named by the ``library`` property). + This is how a custom target, which lives outside of the compiler, supplies its own memory layout. +#. ``/prog8lib//link.ld``, for targets that are built into the compiler. +#. ``/prog8lib/qemu68k/link.ld`` as a fallback, so a target without a script of its own still links. + +A minimal script for a custom target looks like this:: + + ENTRY(prog8_program_start) + SECTIONS + { + . = 0x40000; + .text : { *(.text) } + .data : { + . = ALIGN(4); + *(.data) + . = ALIGN(4); + prog8_bss_section_start = .; + *(.bss) + *(COMMON) + . = ALIGN(2); + prog8_program_end = .; + } + } + +``ENTRY`` should point at ``prog8_program_start``, the label the compiler emits for the start of +the program. The ``.data`` and ``.bss`` sections are deliberately merged into a single output +section so that the linker places them in one load segment: if ``.bss`` were its own output section, +the linker would compute that segment's address as "end of the previous segment + alignment" instead +of honouring the address in the script, and ``prog8_program_end`` would no longer point at the true +end of the loaded image. For the same reason ``prog8_program_end`` is defined by the linker script +rather than by the generated assembly. Startup code that needs to clear the bss, such as the +``qemu68k`` bootinfo support, references that symbol, so leave it in. + +The complete script used by the ``qemu68k`` target, together with a description of the linking +steps, is in :source:`the m68k linking documentation `. + +Source level restrictions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The m68k code generator rejects a number of directives and options that only make sense for a +6502 target: the ``%launcher``, ``%address``, ``%memtop`` and ``%varsaddress`` directives, register +annotations on ordinary subroutine parameters (they are only used in ``asmsub``), split word arrays +written as ``&>``, and specific block addresses. The ``-varsgolden``, ``-varshigh`` and +``-varsaddress`` command line options are rejected as well, so where the variables are placed is +decided by the linker script instead. + Memory Model ============