Each element mentioned in this section are declared inside c64lib namespace.
This is to avoid potential name clashes with other libraries and/or with client assembly programs.
Kick Assembler allows to access namespaced objects via c64lib. prefix, but this, unfortunately, works for labels only.
All other elements such as functions and macros can be only accessed from within c64lib namespace.
If accessing elements via c64lib namespace is, by any reason, not possible nor convenient, there are also so-called "global" declarations, where certain elements of the library are exposed with global names (thus accessible in global namespace).
All such global declarations have names prefixed with c64lib_ prefix, to avoid name clashes.
A label is a value tagged with name. Labels are used to give symbolic names to values denoting addresses, registers or constants. Labels are useful to define memory locations and are widely used in chipset library:
.label VIC2 = $D000 .label SPRITE_0_X = VIC2 + $00 .label SPRITE_0_Y = VIC2 + $01 .label SPRITE_1_X = VIC2 + $02 .label SPRITE_1_Y = VIC2 + $03 .label SPRITE_2_X = VIC2 + $04 .label SPRITE_2_Y = VIC2 + $05
Labels are always declared inside c64lib namespace.
Labels can be reached outside c64lib namespace by prefixing their names with namespace name (labels are the only elements that can be accessed that way due to Kick Assembler limitations).
For example, at any time it is legal to write:
lda #100 sta c64lib.SPRITE_0_X
A function is an element of Kick Assembler that is declared using .function keyword.
Functions does not assemble into any machine code - they are used by assembler to evaluate values that can be then used in other functions, macros or to assembly a code.
.function neg(value) {
.return value ^ $FF
}
/*
* Params:
* video: location of video ram: 0..15
* bitmap: location of bitmap definition: 0..1
*/
.function getBitmapMemory(video, bitmap) {
.return bitmap<<3 | video<<4
}
Functions are always declared inside c64lib namespace.
Functions are declared in files .asm files located under lib directory.
A macro is an element of Kick Assembler that is declared using .macro directive.
Once used, macro is replaced with assembly code defining it.
Macros can be parametrised (that is, they can take an argument, or even more arguments) - this parametrisation can affect the code being generated.
It is noteworthy that macro is not an equivalent of subroutine, even though it looks similar to one from syntactic point of view. When macro is “called”:
someFancyMacro(parameter1, parameter2)
it does not mean, that your code will jump into place where macro someFancyMacro is declared, pass both parameters and return from that place once execution is finished.
Instead, an assembler will paste code declared inside macro substituting parameters with values provided as parameter1 and parameter2.
Under some circumstances you can use macros as subroutines. This may be fast, because there is no subroutine calling overhead. This will however consume a lot of memory (in sense of generated machine code) if not used wisely.
Macros are declared in .asm files located under lib directory.
Subroutine is a consistent set of assembly instructions that performs concrete operation. The major difference between plain macro and subroutine is that macro is used to substitute commonly used patterns of intructions (at cost of growing machine code size) and subroutine is used to save on machine code size actually.
As subroutine we understand a piece of ML code that can be used by jumping there with jsr operation.
A subroutine always ends with rts which means that at the end of execution program counter will be restored to the position right after original jsr operation and code execution will continue.
In this sense a subroutine is an equivalent of procedure, function or method in high level programming languages.
Kick Assembler as such does not provide any special means to create subroutines as it is just a macro assembler.
With c64lib we basically share subroutine code just by writing piece of asm code and place it in separate source files.
Subroutines are declared in .asm files located under lib/dir subdirectory.
Each subroutine consists of appropriate rts operation so that it should always be accessed with corresponding jsr operation.
If soubroutine consumes input parameters, they should be set accordingly before jsr is executed.
Depending on the parameter passing method it should be either register setup (that is A, X or Y), memory location setup or pushing to the stack.
For stack method there is a convenience library invoke available.
A subroutine code should be imported in place where it needs to be located - we don’t do it at the top of the source file but rather we use #import directive exactly in place where we want to have our subroutine.
Lets consider copyLargeMemForward subroutine as an example.
We have to label a place in memory where the subroutine will start and then import the subroutine itself:
copyMemFwd:
#import "common/lib/sub/copyLargeMemForward.asm"
The subroutine takes three parameters using stack passing method:
-
Stack WORD - source address
-
Stack WORD - target address
-
Stack WORD - size
So, before calling subroutine, you have to push 6 bytes to the stack. The easiest way to do it is to use invoke library:
#import "common/lib/invoke-global.asm"
and then:
c64lib_pushParamW(sourceAddress) c64lib_pushParamW(destinationAddress) c64lib_pushParamW(amountOfBytesToCopy) jsr copyMemFwd
In result a subroutine will be called and amountOfBytesToCopy bytes will be copied from sourceAddress location to the destinationAddress location.
Some subroutines use this convenient method of distribution. Instead of being declared in separate source file, they are declared where macros and functions are declared - in library source files itself.
Macro-hosted subroutines are used when further parametrisation is needed before subroutine is ready to use. Usually there are some variants that can be turned on or off (in such case such macro can be called multiple times thus generating multiple versions of subroutine). Sometimes subroutine requires some zero-page addresses that we don’t want to hardcode in the library - it would be then up to the user to parametrise subroutine with addresses of choice.
Let’s consider scroll1x1:
This subroutine requires three parameters being passed via stack but also needs two consecutive bytes on zero page for functioning (indirect addressing is used). Let’s assume we will use address 4 and 5 for this purpose.
#import "text/lib/scroll1x1.asm" #import "common/lib/invoke.asm"
...
.namespace c64lib {
pushParamW(screenAddress)
pushParamW(textAddress)
pushParamWInd(scrollPtr)
}
jsr scroll
...
scroll: .namespace c64lib { scroll1x1(4) }
So, the scroll subroutine is configured for address 4 (and 5), and installed under address denoted by scroll label.
It can be then normally called with jsr scroll.
Before calling input parameters need to be pushed to the stack.
It is done via pushParamW macros (for address values) and pushParamWInd (to extract value from memory location pointed by parameter).