Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.
This repository was archived by the owner on Dec 11, 2023. It is now read-only.

[feature / query] singleton macros for library functions? #210

@nww02

Description

@nww02

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 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 
            .
        .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. :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions