mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
support custom m68k targets: select the codegen by CpuType and allow a per-target link.ld
This commit is contained in:
1 parent
5222562493
commit
ec4b47ac0d
10 files changed
+169
-76
No files matched your search
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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/<targetname>/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
|
||||
|
||||
@@ -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/<targetname>/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<String>, 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",
|
||||
|
||||
@@ -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<String>): 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<String>): 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)
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
|
||||
@@ -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/<targetname>/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 <codeGenM68k/docs/linking.md>`.
|
||||
|
||||
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
|
||||
============
|
||||
|
||||
Reference in new issue
Block a user