mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
300 lines
10 KiB
ReStructuredText
300 lines
10 KiB
ReStructuredText
.. _loadable_library:
|
|
|
|
*************************
|
|
Binary Loadable Libraries
|
|
*************************
|
|
.. index:: pair: Libraries; Overview
|
|
|
|
**also called 'Library Blobs'.**
|
|
|
|
.. note::
|
|
Binary loadable libraries are a facility for the 6502-based targets only. They exist to work
|
|
around those systems' limited amount of RAM, by loading parts of a program on demand into a
|
|
fixed memory area. The m68k targets do not support them (``%output library`` is an error there)
|
|
and have no need for them: there is plenty of RAM available to simply create large programs
|
|
in one piece.
|
|
|
|
Prog8 allows you to create binary library files that contain routines callable by other programs.
|
|
Those programs can be written in Prog8, BASIC, or something else. They just LOAD the binary library
|
|
file into memory, and call the routines.
|
|
|
|
An example of a library file loaded in BASIC on the Commander X16:
|
|
|
|
.. image:: _static/x16library.png
|
|
:align: center
|
|
|
|
(On the Commodore-64 and such, it works identically but you have to type the SYS addresses in decimal notation)
|
|
|
|
|
|
Requirements
|
|
^^^^^^^^^^^^
|
|
.. index:: single: Libraries; Requirements
|
|
|
|
Such a loadable library has to adhere to a few rules:
|
|
|
|
It can't use zero page variables
|
|
Otherwise it might overwrite variables being used by the calling program.
|
|
For systems that have the 16 'virtual registers' cx16.r0-r15 in zero page:
|
|
these 16 words are free to use. For other systems, only the internal prog8
|
|
zeropage scratch variables can be used.
|
|
*note: this may be improved upon in a future version*
|
|
|
|
No system initialization and startup code
|
|
The library cannot perform any regular "system initialization" that normal
|
|
Prog8 programs usually perform (such as resetting the IO registers, clearing the screen,
|
|
changing the colors, and other initialization logic). This would disturb the
|
|
state of the calling program! The library can (must) assume that that the calling
|
|
program has already done all required initialization.
|
|
|
|
Variable initialization
|
|
The library still has to initialize any variables it might use and clear
|
|
uninitialized "BSS" variables! Otherwise the code will not run predictably as prog8 code.
|
|
The library must therefore have a ``start()`` entrypoint that must be called
|
|
**before any other library routine** can be used. This is described in detail in
|
|
the :ref:`Library initialization <library_init>` section below.
|
|
|
|
Binary output and loaded into a fixed memory address
|
|
The library must not have a launcher such as a BASIC SYS command, because
|
|
it is not ran like a normal program.
|
|
Also, because it is not possible to create position independent code with prog8,
|
|
a fixed load address has to be decided on and the library must be compiled
|
|
with that address as the load address.
|
|
|
|
|
|
``%output library``
|
|
^^^^^^^^^^^^^^^^^^^
|
|
.. index:: single: Libraries; %output library
|
|
|
|
Most of the above requirements can be fulfilled by setting various directives in your
|
|
source code such as %launcher, %zeropage and so on. But there is a single directive that does it correctly for you in one go
|
|
(and makes sure there won't be any initialization code left at all): ``%output library``
|
|
|
|
Together with ``%address`` and possibly ``%memtop`` -to tell the compiler what the load address of the library should be-
|
|
it will create a binary output file named after the program (``<programname>.bin``) that fulfills the requirements of a loadable binary library program as listed above.
|
|
(The exact file name and location can be overridden with the ``-o`` command line option.)
|
|
|
|
For older CBM targets (C64, C128 and PET) the library file *will* have a load address header,
|
|
because these targets require a header to easily load files. For the other targets such as the Commander X16,
|
|
the library will be a headerless binary file that can then be loaded given the correct load address.
|
|
|
|
|
|
|
|
|
|
.. _library_init:
|
|
|
|
Library initialization
|
|
^^^^^^^^^^^^^^^^^^^^^^
|
|
.. index:: single: Libraries; Initialization
|
|
|
|
A library blob **must** have a ``start()`` subroutine. When the entrypoint is called,
|
|
the Prog8 runtime automatically clears the library's BSS section and initializes
|
|
variables before ``start()`` itself runs. Even an empty ``start()`` is sufficient --
|
|
the BSS clearing happens as part of the startup code generated by the compiler.
|
|
Without calling this entrypoint, all library variables and BSS data contain garbage
|
|
and the library code will not work correctly.
|
|
|
|
The compiler automatically inserts a ``JMP`` instruction to the ``start()`` subroutine
|
|
at the very first address of the library (its entrypoint). For a library loaded at ``$A000``,
|
|
this means ``JSR $A000`` runs the initialization and ``$A000`` is the init routine's call address.
|
|
|
|
After loading the library into memory, **you must call the init routine at address 0** (e.g. ``$A000``)
|
|
before calling any other library routine. This call can be a plain ``JSR`` because it happens
|
|
while the library's memory is already mapped at its load address.
|
|
|
|
From Prog8, declare it as a plain ``extsub`` (not ``@bank``)::
|
|
|
|
extsub $A000 = lib_init()
|
|
|
|
From BASIC:
|
|
|
|
.. code-block:: text
|
|
|
|
SYS $A000 : REM TO INITIALIZE VARIABLES, REQUIRED!
|
|
|
|
See the examples in the next section for complete usage patterns.
|
|
|
|
The following example library source shows a minimal ``start()``::
|
|
|
|
%address $A000
|
|
%memtop $C000
|
|
%output library
|
|
|
|
%import textio
|
|
|
|
main {
|
|
sub start() {
|
|
; has to be here for initialization
|
|
txt.print("lib initialized\n")
|
|
}
|
|
}
|
|
|
|
|
|
Jump table
|
|
^^^^^^^^^^
|
|
.. index:: single: Libraries; Jump table
|
|
|
|
.. note::
|
|
The ``%jmptable`` directive described below is currently only supported on 6502 targets (c64, cx16, c128, pet32).
|
|
It is not available on M68K targets (amiga500, amiga1200, qemu68k) because loadable library support
|
|
has not been implemented for those platforms yet.
|
|
|
|
For ease of use, libraries should probably have a fixed "jump table" where the offsets of the
|
|
library routines stay the same across different versions of the library. Without needing new syntax,
|
|
there's a trick in Prog8 that you can use to build such a jumptable:
|
|
add a non-split word array at the top of the library main block that contains JMP instructions
|
|
and the addresses of the individual library subroutines. Do NOT change the order of the subroutines
|
|
in this table!
|
|
As described in the previous section, the compiler will already have placed a ``JMP`` to the
|
|
``start()`` subroutine at address 0. The jump table entries start immediately after that,
|
|
so the first jump table entry is at address +3 (e.g. ``$A003``).
|
|
Because the compiler places the jumptable right after the startup JMP instruction,
|
|
it looks as though the init call is just the first entry in the jump table --
|
|
which is quite convenient.
|
|
|
|
Look at the generated assembly code to see exactly what is going on.
|
|
But the users of the library are none the wiser and it just seems as if it is part of the jump table in a natural way :-)
|
|
|
|
Loading and using the library
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.. index:: single: Libraries; Loading and using
|
|
|
|
These examples below assume the target is the Commander X16.
|
|
Assuming the load address of the library is $A000:
|
|
|
|
.. index:: single: Examples; Library from BASIC
|
|
|
|
**From BASIC**:
|
|
|
|
.. code-block:: text
|
|
|
|
BLOAD "LIBRARY.BIN",8,1,$A000
|
|
SYS $A000 : REM TO INITIALIZE VARIABLES, REQUIRED!
|
|
SYS $A003 : REM CALL FIRST ROUTINE
|
|
SYS $A006 : REM CALL SECOND ROUTINE, ETC.
|
|
|
|
|
|
.. index:: single: Examples; Library from Prog8
|
|
|
|
**From Prog8:**
|
|
|
|
The ``diskio`` module actually provides a convenience routine called ``loadlib`` that loads a Prog8-compiled
|
|
library blob into memory. It internally automatically uses either regular load() or load_raw(),
|
|
as required by the compilation target (so you don't have to bother with target machine
|
|
differences if you want to write portable code)::
|
|
|
|
%import diskio
|
|
|
|
main {
|
|
|
|
extsub $A000 = lib_init() clobbers(A)
|
|
extsub $A003 = lib_func1() clobbers(A,X,Y)
|
|
extsub $A006 = lib_func2() clobbers(A,X,Y)
|
|
|
|
sub start() {
|
|
if diskio.loadlib("library.bin", $a000) != 0 {
|
|
lib_init()
|
|
lib_func1()
|
|
lib_func2()
|
|
|
|
repeat { }
|
|
}
|
|
}
|
|
}
|
|
|
|
.. index:: single: Examples; Library from C
|
|
|
|
**From C**:
|
|
|
|
.. code-block:: c
|
|
|
|
#include <cbm.h>
|
|
|
|
int main() {
|
|
void (*lib_init)(void) = (void (*)()) 0xa000;
|
|
void (*lib_func1)(void) = (void (*)()) 0xa003;
|
|
void (*lib_func2)(void) = (void (*)()) 0xa006;
|
|
|
|
cbm_k_setlfs(0, 8, 2);
|
|
cbm_k_setnam("library.bin");
|
|
cbm_k_load(0, 0xa000);
|
|
|
|
lib_init();
|
|
lib_func1();
|
|
lib_func2();
|
|
return 0;
|
|
}
|
|
|
|
.. index:: single: Examples; Library from Assembly
|
|
|
|
**From Assembly**:
|
|
|
|
.. code-block:: text
|
|
|
|
; add error handling as desired.
|
|
ldy #>libname
|
|
ldx #<libname
|
|
lda #11
|
|
jsr $ffbd ; SETNAM
|
|
lda #0
|
|
ldx #8
|
|
ldy #2 ; load address override
|
|
jsr $ffba ; SETLFS
|
|
lda #0
|
|
ldx #<$a000
|
|
ldy #>$a000
|
|
jsr $ffd5 ; LOAD
|
|
lda #13
|
|
jsr $ffd2 ; CHROUT
|
|
|
|
jsr $A000 ; library init
|
|
jsr $A003 ; lib func 1
|
|
jsr $A006 ; lib func 2
|
|
|
|
rts
|
|
|
|
libname:
|
|
.text "library.bin"
|
|
|
|
|
|
Example library code
|
|
^^^^^^^^^^^^^^^^^^^^
|
|
.. index:: single: Examples; small example library
|
|
|
|
Here is the small example library that was used in the example at the beginning of this chapter::
|
|
|
|
%address $A000
|
|
%memtop $C000
|
|
%output library
|
|
|
|
%import textio
|
|
|
|
|
|
main {
|
|
; Create a jump table as first thing in the library.
|
|
; NOTE: the compiler has inserted a single JMP instruction at the start
|
|
; of the 'main' block, that jumps to the start() routine.
|
|
; This is convenient because the rest of the jump table simply follows it,
|
|
; making the first jump neatly be the required initialization routine
|
|
; for the library (initializing variables and BSS region).
|
|
%jmptable (
|
|
library.func1,
|
|
library.func2,
|
|
)
|
|
|
|
sub start() {
|
|
; has to be here for initialization
|
|
txt.print("lib initialized\n")
|
|
}
|
|
}
|
|
|
|
|
|
library {
|
|
sub func1() {
|
|
txt.print("lib func 1\n")
|
|
}
|
|
|
|
sub func2() {
|
|
txt.print("lib func 2\n")
|
|
}
|
|
}
|