spectnetide
spectnetide copied to clipboard
[feature / query] singleton macros for library functions?
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
#endif
Obviously 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
.endm
If used, the library_function_body macro is injected inline ONCE and all calls treat it like a normal function call (using $CD
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
.
.endm
The 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. :)
@nww02, sorry for ignoring your request for such a long time :-(. I had hard weeks behind me. However, now, I have time again for this project :-).
I like the idea of singleton macros the way you described. Here are my comments for the design:
- I'd allow a singleton macro only with no parameters (at least for the first implementation).
- I'd use this syntax:
some_library_function:
.macro() .singleton
; The body of the library function
; The compiler closes it with a RET (at least in the first version)
.endm
Just as you suggested, I'd place the code body only once (the first time the singleton macro is involved. Assume, you have this code:
ld a,1
some_library_function()
ld hl,$4000
ld a,2
some_library_function()
The assembler would create this code:
ld a,1
jp _after_library_body
some_library_function:
; The body of the library function
; The compiler closes it with a RET (at least in the first version)
ret
after_library_body:
call some_library_function
ld hl,$4000
ld a,2
call some_library_function()
Please, add your comments!
That looks really good.
In libraries I would create a "header" macro that used your incredibly powerful is*() to rearrange and sort the parameters (effectively giving a form of polymorphism) into a particular structure, and then calling the primary library singleton to do all the work. Since your code already optimises the macros. it'd make for some really sleak code, but with quite a powerful structure around it.
Even if you found no other improvements, having the ability to make libraries with intelligent inline inclusion would be awesome. I hope you are able to find it possible to implement :)