-
Notifications
You must be signed in to change notification settings - Fork 30
[feature / query] singleton macros for library functions? #210
Description
I was considering how to make libraries more portable, and I've been tinkering with the lovely isreg* macro directives.
Macros are great for creating re-wirable, flexible and re-usable code, but aren't so useful for libraries as they drop their code inline. I couldn't figure a good way of conditionally including functions from a library.
I considered creating a macro 'header' which then executed a library function having. In this way, there'd be a single large library function included just once that does all the hard work, but a little header macro can shuffle the parameters and do type-checking before calling the main function.
This DOES work very well, however there's no tidy way of conditionally compiling-in the main library body. I have to use #ifdefs, which means using #defines for every function I want to use... messy.
I considered something like using a preprocessor directive inside the macro, but this doesn't work because (I presume) the pre-processor step happens before the macro step:-
// A header macro which does some setup, like
// checking the parameter values or pushing
// registers onto the stack, or pulling
// in parameters from storage etc..
some_library_function: // The header
.macro(instr)
#define _include_main_function_body
{{instr}}
call library_function_body // standard fn call...
.endm
// This is the main library function. It's assumed that the macro does
// the usage-specific stuff, and the library body does the heavy lifting.
#ifdef _include_main_function_body
library_function_body:
ld c,1
ret
#endifObviously this doesn't work, I need to do the #define outside the macro :'(
So I wondered: is there a way you can specify that a macro is to be injected into the code ONCE and only once.. In that way, the evaluation of the code inclusion would be LIKE a #define, but coincident with the macro evaluation...
// This is the header. This is what the user would call.
// In this instance it is a dummy, intended to just call
// the body to demonstrate what I mean.
some_library_function:
.macro()
library_function_body() // macro-call this time!
.endm
// The main library function.
library_function_body:
.singleton_macro()
ld c,1
.endmIf used, the library_function_body macro is injected inline ONCE and all calls treat it like a normal function call (using $CD to call it and a 201 ret added at the end). Because it's included inline, there'd need to be a jump over it once it had been called: a jp to a trailing nop to hop over the included function body when control returns to the first instance of the macro header:.
To demonstrate: if a macro was used 3 times like this:-
some_library_function()
some_library_function()
some_library_function()and some_library_function() contained this:-
some_library_function:
.macro(params)
.
some_library_function code BEFORE function_body call
.
library_function_body()
.
some_library_function code AFTER function_body call
.
.endmThe compiled code layout would look like:
some_library_function code BEFORE function_body call // First invocation of the macro
call function_body // calls the body
jp skip_over_function_body: // inserted to skip the function body
function_body // The actual library code to be included once
ret // A RET instruction so it can be re-called from elsewhere
skip_over_function_body: nop // the jump target from earlier
some_library_function code AFTER function_body call
some_library_function code BEFORE function_body call // second invocation of the macro
call library_function_body // calls the function body, but DOES NOT inline it this time
some_library_function code AFTER function_body call
some_library_function code BEFORE function_body call // third invocation of the macro
call library_function_body // calls the function body, but DOES NOT inline it this time
some_library_function code AFTER function_body call
The total additional code cost would be about 8 bytes for the call, ret, jp and nop if it's used once, but if it's used twice the code saving would start to mount up.
I imagine this poses a problem for macro type-checking and parameter checking in the library singleton_macro.... So if singleton macros were barred from taking parameters, that might be acceptable as a workaround.
Perhaps if it works well, then maybe in a later iteration, the parameter checking could be done on the FIRST call, to lay out the code, and then if it's called differently in a subsequent call, unexpected results would be expected....
I'm interested to hear your thoughts, and/or if there's a cleaner way of achieving the same aim... I've been away for a while, but would be good to get back to doing my z80 libraries. :)