add %output library

This commit is contained in:
Irmen de Jong 2025-01-24 23:25:57 +01:00
parent 1e17df5296
commit 2478aea316
8 changed files with 156 additions and 242 deletions

View File

@ -317,7 +317,8 @@ val CpuRegisters = arrayOf(
enum class OutputType {
RAW,
PRG,
XEX
XEX,
LIBRARY
}
enum class CbmPrgLauncherType {

View File

@ -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()))

View File

@ -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""")
}
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""")
}
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")
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")
}
}
}
}

View File

@ -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,

View File

@ -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?)

View File

@ -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
}
}
}
}
}
*/

View File

@ -90,6 +90,7 @@ class IRFileReader {
val zpReserved = mutableListOf<UIntRange>()
val zpAllowed = mutableListOf<UIntRange>()
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
)

View File

@ -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?