From 2478aea316e1cccae34bbf877df71b01bbbbe111 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 24 Jan 2025 23:25:57 +0100 Subject: [PATCH] add %output library --- codeCore/src/prog8/code/core/Enumerations.kt | 3 +- .../prog8/codegen/cpu6502/AssemblyProgram.kt | 5 + .../codegen/cpu6502/ProgramAndVarsGen.kt | 176 ++++++++++-------- compiler/src/prog8/compiler/Compiler.kt | 40 +++- docs/source/todo.rst | 20 +- examples/test.p8 | 149 +-------------- .../src/prog8/intermediate/IRFileReader.kt | 4 +- .../src/prog8/intermediate/IRFileWriter.kt | 1 + 8 files changed, 156 insertions(+), 242 deletions(-) diff --git a/codeCore/src/prog8/code/core/Enumerations.kt b/codeCore/src/prog8/code/core/Enumerations.kt index 712ad854b..c2fc9544e 100644 --- a/codeCore/src/prog8/code/core/Enumerations.kt +++ b/codeCore/src/prog8/code/core/Enumerations.kt @@ -317,7 +317,8 @@ val CpuRegisters = arrayOf( enum class OutputType { RAW, PRG, - XEX + XEX, + LIBRARY } enum class CbmPrgLauncherType { diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt index 6efb4e0db..92365f680 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt @@ -58,6 +58,11 @@ internal class AssemblyProgram( println("\nCreating raw binary for target ${compTarget.name}.") binFile } + OutputType.LIBRARY -> { + command.add("--nostart") + println("\nCreating binary library file for target ${compTarget.name}.") + binFile + } else -> throw AssemblyError("invalid output type") } command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString())) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt index 35bfa6f88..eb840f839 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt @@ -87,93 +87,107 @@ internal class ProgramAndVarsGen( } } - when(options.output) { - OutputType.RAW -> { - asmgen.out("; ---- raw assembler program ----") - asmgen.out("* = ${options.loadAddress.toHex()}") - asmgen.out("prog8_program_start\t; start of program label") - asmgen.out(" cld") - asmgen.out(" tsx ; save stackpointer for sys.exit()") - asmgen.out(" stx prog8_lib.orig_stackpointer") - if(!options.noSysInit) - asmgen.out(" jsr p8_sys_startup.init_system") - asmgen.out(" jsr p8_sys_startup.init_system_phase2") - } - OutputType.PRG -> { - when(options.launcher) { - CbmPrgLauncherType.BASIC -> { - if (options.loadAddress != options.compTarget.PROGRAM_LOAD_ADDRESS) { - errors.err("BASIC output must have load address ${options.compTarget.PROGRAM_LOAD_ADDRESS.toHex()}", program.position) + if(options.output == OutputType.LIBRARY) { + + asmgen.out("; ---- library assembler program ----") + asmgen.out("* = ${options.loadAddress.toHex()}") + asmgen.out(" jmp p8b_main.p8s_start") // TODO still needed otherwise 64tass removes all .procs + + } else { + + when (options.output) { + OutputType.LIBRARY -> { } + OutputType.RAW -> { + asmgen.out("; ---- raw assembler program ----") + asmgen.out("* = ${options.loadAddress.toHex()}") + asmgen.out("prog8_program_start\t; start of program label") + asmgen.out(" cld") + asmgen.out(" tsx ; save stackpointer for sys.exit()") + asmgen.out(" stx prog8_lib.orig_stackpointer") + if (!options.noSysInit) + asmgen.out(" jsr p8_sys_startup.init_system") + asmgen.out(" jsr p8_sys_startup.init_system_phase2") + } + OutputType.PRG -> { + when (options.launcher) { + CbmPrgLauncherType.BASIC -> { + if (options.loadAddress != options.compTarget.PROGRAM_LOAD_ADDRESS) { + errors.err( + "BASIC output must have load address ${options.compTarget.PROGRAM_LOAD_ADDRESS.toHex()}", + program.position + ) + } + asmgen.out("; ---- basic program with sys call ----") + asmgen.out("* = ${options.loadAddress.toHex()}") + asmgen.out("prog8_program_start\t; start of program label") + val year = LocalDate.now().year + asmgen.out(" .word (+), $year") + asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'") + asmgen.out("+\t.word 0") + asmgen.out("prog8_entrypoint") + asmgen.out(" cld") + asmgen.out(" tsx ; save stackpointer for sys.exit()") + asmgen.out(" stx prog8_lib.orig_stackpointer") + if (!options.noSysInit) + asmgen.out(" jsr p8_sys_startup.init_system") + asmgen.out(" jsr p8_sys_startup.init_system_phase2") + } + + CbmPrgLauncherType.NONE -> { + // this is the same as RAW + asmgen.out("; ---- program without basic sys call ----") + asmgen.out("* = ${options.loadAddress.toHex()}") + asmgen.out("prog8_program_start\t; start of program label") + asmgen.out(" cld") + asmgen.out(" tsx ; save stackpointer for sys.exit()") + asmgen.out(" stx prog8_lib.orig_stackpointer") + if (!options.noSysInit) + asmgen.out(" jsr p8_sys_startup.init_system") + asmgen.out(" jsr p8_sys_startup.init_system_phase2") } - asmgen.out("; ---- basic program with sys call ----") - asmgen.out("* = ${options.loadAddress.toHex()}") - asmgen.out("prog8_program_start\t; start of program label") - val year = LocalDate.now().year - asmgen.out(" .word (+), $year") - asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'") - asmgen.out("+\t.word 0") - asmgen.out("prog8_entrypoint") - asmgen.out(" cld") - asmgen.out(" tsx ; save stackpointer for sys.exit()") - asmgen.out(" stx prog8_lib.orig_stackpointer") - if(!options.noSysInit) - asmgen.out(" jsr p8_sys_startup.init_system") - asmgen.out(" jsr p8_sys_startup.init_system_phase2") - } - CbmPrgLauncherType.NONE -> { - // this is the same as RAW - asmgen.out("; ---- program without basic sys call ----") - asmgen.out("* = ${options.loadAddress.toHex()}") - asmgen.out("prog8_program_start\t; start of program label") - asmgen.out(" cld") - asmgen.out(" tsx ; save stackpointer for sys.exit()") - asmgen.out(" stx prog8_lib.orig_stackpointer") - if(!options.noSysInit) - asmgen.out(" jsr p8_sys_startup.init_system") - asmgen.out(" jsr p8_sys_startup.init_system_phase2") } } + OutputType.XEX -> { + asmgen.out("; ---- atari xex program ----") + asmgen.out("* = ${options.loadAddress.toHex()}") + asmgen.out("prog8_program_start\t; start of program label") + asmgen.out(" cld") + asmgen.out(" tsx ; save stackpointer for sys.exit()") + asmgen.out(" stx prog8_lib.orig_stackpointer") + if (!options.noSysInit) + asmgen.out(" jsr p8_sys_startup.init_system") + asmgen.out(" jsr p8_sys_startup.init_system_phase2") + } } - OutputType.XEX -> { - asmgen.out("; ---- atari xex program ----") - asmgen.out("* = ${options.loadAddress.toHex()}") - asmgen.out("prog8_program_start\t; start of program label") - asmgen.out(" cld") - asmgen.out(" tsx ; save stackpointer for sys.exit()") - asmgen.out(" stx prog8_lib.orig_stackpointer") - if(!options.noSysInit) - asmgen.out(" jsr p8_sys_startup.init_system") - asmgen.out(" jsr p8_sys_startup.init_system_phase2") - } - } - if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) { - asmgen.out(""" - ; zeropage is clobbered so we need to reset the machine at exit - lda #>sys.reset_system - pha - lda #sys.reset_system + pha + lda # { - if(options.floats) - asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in - asmgen.out(" jsr p8b_main.p8s_start") - asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") - } - "c64" -> { - asmgen.out(" jsr p8b_main.p8s_start") - asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") - } - "c128" -> { - asmgen.out(" jsr p8b_main.p8s_start") - asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") - } - else -> { - asmgen.out(" jsr p8b_main.p8s_start") - asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") + when (compTarget.name) { + "cx16" -> { + if (options.floats) + asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in + asmgen.out(" jsr p8b_main.p8s_start") + asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") + } + "c64" -> { + asmgen.out(" jsr p8b_main.p8s_start") + asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") + } + "c128" -> { + asmgen.out(" jsr p8b_main.p8s_start") + asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") + } + else -> { + asmgen.out(" jsr p8b_main.p8s_start") + asmgen.out(" jmp p8_sys_startup.cleanup_at_exit") + } } } } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 082de167c..eed34fedf 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -253,6 +253,15 @@ internal fun determineProgramLoadAddress(program: Program, options: CompilationO throw AssemblyError("atari xex output can't contain BASIC launcher") loadAddress = options.compTarget.PROGRAM_LOAD_ADDRESS } + OutputType.LIBRARY -> { + if(options.launcher!=CbmPrgLauncherType.NONE) + throw AssemblyError("library output can't contain BASIC launcher") + if(options.zeropage!=ZeropageType.DONTUSE) + throw AssemblyError("library output can't use zeropage") + if(options.noSysInit==false) + throw AssemblyError("library output can't have sysinit") + // LIBRARY has no predefined load address. + } } } @@ -264,7 +273,7 @@ internal fun determineProgramLoadAddress(program: Program, options: CompilationO } if(loadAddress==null) { - errors.err("load address must be specified with the specifid output/launcher options", program.toplevelModule.position) + errors.err("load address must be specified for the selected output/launcher options", program.toplevelModule.position) return } @@ -334,10 +343,19 @@ fun parseMainModule(filepath: Path, importer.importImplicitLibraryModule("verafx") } - if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG) - errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position) - if(compilerOptions.launcher == CbmPrgLauncherType.BASIC && compTarget.name== AtariTarget.NAME) - errors.err("atari target cannot use CBM BASIC launcher, use NONE", program.toplevelModule.position) + if(compilerOptions.output==OutputType.LIBRARY) { + if(compilerOptions.launcher != CbmPrgLauncherType.NONE) + errors.err("library must not use a launcher", program.toplevelModule.position) + if(compilerOptions.zeropage != ZeropageType.DONTUSE) + errors.err("library cannot use zeropage", program.toplevelModule.position) + if(compilerOptions.noSysInit == false) + errors.err("library cannot use sysinit", program.toplevelModule.position) + } else { + if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG) + errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position) + if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compTarget.name == AtariTarget.NAME) + errors.err("atari target cannot use CBM BASIC launcher, use NONE", program.toplevelModule.position) + } errors.report() @@ -354,8 +372,8 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat as? Directive)?.args?.single()?.name?.uppercase() val allOptions = program.modules.flatMap { it.options() }.toSet() val floatsEnabled = "enable_floats" in allOptions - val noSysInit = "no_sysinit" in allOptions - val zpType: ZeropageType = + var noSysInit = "no_sysinit" in allOptions + var zpType: ZeropageType = if (zpoption == null) if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE else @@ -395,7 +413,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat OutputType.PRG } } - val launcherType = if (launcherTypeStr == null) { + var launcherType = if (launcherTypeStr == null) { when(compTarget) { is AtariTarget -> CbmPrgLauncherType.NONE else -> CbmPrgLauncherType.BASIC @@ -409,6 +427,12 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat } } + if(outputType == OutputType.LIBRARY) { + launcherType = CbmPrgLauncherType.NONE + zpType = ZeropageType.DONTUSE + noSysInit = true + } + return CompilationOptions( outputType, launcherType, zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit, diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 8198d489d..2bdcb2774 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,6 +1,19 @@ TODO ==== +- added %output library, which preselects a bunch of options required to build a library file (rather than an executable program): + %launcher none + %option no_sysinit + %zeropage dontuse + You still have to set %address and %memtop yourself to tell prog8 where the library is meant to be placed in memory + TODO: it's now a raw binary file, should it include the 2-byte PRG header for easy loading from basic as well perhaps? + +- Compiling Libraries: improve ability to create library files in prog8; for instance there's still stuff injected into the start of the start() routine (see translateSubroutine function) + AND there is separate setup logic going on before calling it. Make up our mind! + Maybe all setup does need to be put into start() ? because the program cannot function correctly when the variables aren't initialized properly bss is not cleared etc. etc. + Need to add some way to generate a stable jump table at a given address. + Library must not include prog8_program_start stuff either. Must not require 'start' entrypoint either? Although they need some initialization entry point? + - Make some of the target machine config externally configurable (for 1 new target, the existing ones should stay as they are for the time being) - add paypal donation button as well? @@ -15,13 +28,6 @@ Future Things and Ideas - Kotlin: can we use inline value classes in certain spots? - add float support to the configurable compiler targets - Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions -- Compiling Libraries: improve ability to create library files in prog8; for instance there's still stuff injected into the start of the start() routine (see translateSubroutine function) - AND there is separate setup logic going on before calling it. Make up our mind! - Maybe all setup does need to be put into start() ? because the program cannot function correctly when the variables aren't initialized properly bss is not cleared etc. etc. - Add a -library $xxxx command line option (and/or some directive) to preselect every setting that is required to make a library at $xxxx rather than a normal loadable and runnable program? - Need to add some way to generate a stable jump table at a given address. - Need library to not call init_system AND init_system_phase2 not either. - Library must not include prog8_program_start stuff either. Must not require 'start' entrypoint either? Although they need some initialization entry point? - [problematic due to using 64tass:] better support for building library programs, where unused .proc are NOT deleted from the assembly. Perhaps replace all uses of .proc/.pend/.endproc by .block/.bend will fix that with a compiler flag? But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?) diff --git a/examples/test.p8 b/examples/test.p8 index b657dfc08..1133dc8d5 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,151 +1,12 @@ -;%import emudbg +%address $A000 +%memtop $C000 +%output library + %import textio -%option no_sysinit -%zeropage basicsafe -%import math main { sub start() { - uword @shared w1, w2 - ubyte @shared b - - w1 = w2 + b*$0002 - w1 = w2 + b + b -; w2 = (w1 + b as uword) + (b as uword) - - -; cx16.r0 = cx16.r1 + cx16.r0L*2 -; cx16.r0 = cx16.r1 + cx16.r0L*$0002 -; cx16.r0 = cx16.r1 + cx16.r0L + cx16.r0L -; cx16.r0 = cx16.r1 - cx16.r0L*2 -; cx16.r0 = cx16.r1 - cx16.r0L*$0002 -; cx16.r0 = cx16.r1 - cx16.r0L - cx16.r0L + txt.print("hello from library\n") } } - - -/* -mainxxx { - - uword[50] @nosplit warray1 - uword[50] @nosplit warray2 - - sub fill_arrays() { - math.rndseed(999,1234) - for cx16.r0L in 0 to len(warray1)-1 { - warray1[cx16.r0L] = math.rndw() - warray2[cx16.r0L] = cx16.r0L * (100 as uword) - } - warray2[40] = 9900 - warray2[44] = 9910 - warray2[48] = 9920 - } - - sub perf_reset() { - emudbg.reset_cpu_cycles() - } - - sub perf_print() { - cx16.r4, cx16.r5 = emudbg.cpu_cycles() - txt.print_uwhex(cx16.r5, true) - txt.print_uwhex(cx16.r4, false) - txt.nl() - } - - sub start() { - sys.set_irqd() - fill_arrays() - - txt.print("\ngnomesort (words):\n") - perf_reset() - gnomesort_uw(warray1, len(warray1)) - perf_print() - for cx16.r0L in 0 to len(warray1)-1 { - txt.print_uw(warray1[cx16.r0L]) - txt.chrout(',') - } - txt.nl() - - txt.print("\ngnomesort (words) almost sorted:\n") - perf_reset() - gnomesort_uw(warray2, len(warray2)) - perf_print() - for cx16.r0L in 0 to len(warray2)-1 { - txt.print_uw(warray2[cx16.r0L]) - txt.chrout(',') - } - txt.nl() - txt.nl() - - fill_arrays() - - txt.print("\ngnomesort_opt (words):\n") - perf_reset() - gnomesort_uw_opt(warray1, len(warray1)) - perf_print() - for cx16.r0L in 0 to len(warray1)-1 { - txt.print_uw(warray1[cx16.r0L]) - txt.chrout(',') - } - txt.nl() - - txt.print("\ngnomesort_opt (words) almost sorted:\n") - perf_reset() - gnomesort_uw_opt(warray2, len(warray2)) - perf_print() - for cx16.r0L in 0 to len(warray2)-1 { - txt.print_uw(warray2[cx16.r0L]) - txt.chrout(',') - } - txt.nl() - sys.clear_irqd() - repeat { - } - } - - - sub gnomesort_uw(uword values, ubyte num_elements) { - ; TODO optimize this more, rewrite in asm? - ubyte @zp pos = 1 - while pos != num_elements { - uword @requirezp ptr = values+(pos*$0002) - cx16.r0 = peekw(ptr-2) - cx16.r1 = peekw(ptr) - if cx16.r0<=cx16.r1 - pos++ - else { - ; swap elements - pokew(ptr-2, cx16.r1) - pokew(ptr, cx16.r0) - pos-- - if_z - pos++ - } - } - } - - sub gnomesort_uw_opt(uword values, ubyte num_elements) { - ; TODO optimize this more, rewrite in asm? - ubyte @zp pos = 1 - uword @requirezp ptr = values+2 - while pos != num_elements { - cx16.r0 = peekw(ptr-2) - cx16.r1 = peekw(ptr) - if cx16.r0<=cx16.r1 { - pos++ - ptr+=2 - } - else { - ; swap elements - pokew(ptr-2, cx16.r1) - pokew(ptr, cx16.r0) - if pos>1 { - pos-- - ptr-=2 - } - } - } - } -} -*/ diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index abe8f341d..bdbebe735 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -90,6 +90,7 @@ class IRFileReader { val zpReserved = mutableListOf() val zpAllowed = mutableListOf() var loadAddress = target.PROGRAM_LOAD_ADDRESS + var memtop = target.PROGRAM_MEMTOP_ADDRESS var optimize = true var outputDir = Path("") @@ -102,6 +103,7 @@ class IRFileReader { "launcher" -> launcher = CbmPrgLauncherType.valueOf(value) "zeropage" -> zeropage = ZeropageType.valueOf(value) "loadAddress" -> loadAddress = parseIRValue(value).toUInt() + "memtop" -> memtop = parseIRValue(value).toUInt() "zpReserved" -> { val (zpstart, zpend) = value.split(',') zpReserved.add(UIntRange(zpstart.toUInt(), zpend.toUInt())) @@ -127,7 +129,7 @@ class IRFileReader { false, target, loadAddress, - 0xffffu, + memtop, outputDir = outputDir, optimize = optimize ) diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index 02b889679..2dafff99f 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -222,6 +222,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { xml.writeCharacters("zpAllowed=${range.first},${range.last}\n") } xml.writeCharacters("loadAddress=${irProgram.options.loadAddress.toHex()}\n") + xml.writeCharacters("memtop=${irProgram.options.memtopAddress.toHex()}\n") xml.writeCharacters("optimize=${irProgram.options.optimize}\n") xml.writeCharacters("outputDir=${irProgram.options.outputDir.absolute()}\n") // other options not yet useful here?