Hi there, folks! I’ve got another big refactor in the works, and I want to give y’all a heads-up on it. This ONLY affects people doing work on the C code of libswipl itself; it does NOT at all affect:
- Writing Prolog code, regardless of if it’s part of the swipl distribution
- Writing packs/extensions that get loaded into swipl at runtime
- Writing external applications that use the public swipl API to embed Prolog
If you fall only into the above categories in your relationship with SWI-Prolog, you can feel free to ignore the rest of this email! If not, you might want to read on.
With that out of the way; this affects the variable called LD
, which stores all of the per-thread or per-engine persistent state: the “Local Data” structure. At present, there are a number of ways to access it:
- Use the macro
GLOBAL_LD
, which retrieves an always-accessible pointer to the structure, but might need to call inefficient functions to retrieve it. - Use
LOCAL_LD
, which assumes the pointer has already been retrieved into the current function. - Use
LD
, which is defined as a synonym for eitherGLOBAL_LD
orLOCAL_LD
above, depending on the file.
If this function is usingLOCAL_LD
, then its definition needs to come from somewhere:GET_LD
in the body of the function, orPRED_LD
in the body of a native predicate implementation.ARG_LD
at the end of a function prototypes parameter list, and a correspondingPASS_LD
at the end of the callsite arguments list.ARG1_LD
as the single parameter in a function declaration, with a correspondingPASS_LD1
as the single argument.- Using a macro defined in
pl-ldpass.h
, which appendsPASS_LD
orPASS_LD1
as appropriate to the function you’re calling; you are still responsible for providing a localLD
in that function if this is inLOCAL_LD
mode.
It can get confusing. Hence: refactor.
The basic high-level idea is that there should only be two types of functions inside libswipl: those that never need to reference LD
in any way, either in their own code or in functions they call, and functions that get passed LD
as an argument. Effectively, this means that LOCAL_LD
is in effect for all library-internal code, and GET_LD
/PRED_LD
will never need to be used. The one exception is when control passes into the library from external code; if the function call doesn’t provide LD
(or a way to uniquely identify the LD
in question) and it does need access to it, GET_LD
fetches the variable from TLS in a stub function before passing it to the real implementation.
To this end, I’m adding two major new macros that people will need to be familiar with: LDFUNC
(with its associated DECL_LD
and LDFUNC_DECLARATIONS
) and API_STUB
. These combine to effectively make all internal libswipl functions operate in mode 3.4 above; LD
never needs to get passed explicitly, as long as your functions are properly declared.
LDFUNC
is used in the #define
directives that make LD
-passing macros, and they all take exactly the same form:
#define PL_copy_term_ref(from) LDFUNC(PL_copy_term_ref, from)
This takes care both of mangling the function name (so that the internal function referenced as PL_copy_term_ref
won’t conflict with the external API function PL_copy_term_ref
) and ensuring that LD
is declared and passed as appropriate. Every library-internal function that deals with LD
will have one of these #define
macros associated with them, so that you’ll never have to think about whether the function you’re calling needs a PASS_LD
or not. The #define
lives either with the header prototype, if it’s a library-internal function, with the forward declaration of a static function at the top of a file, or with the function definition itself, if it’s static and doesn’t need a forward declaration. That way there shouldn’t ever be confusion about whether a function has an LD
-passing shorthand or not; they all do.
Declaring the passed-LD
argument for such a function takes one of two forms. For standalone prototypes, like in a header file or a block of static forward declarations, you can surround the list of prototypes with a define/undef for the macro LDFUNC_DECLARATIONS
, and in that case the function prototypes themselves don’t need any modification, or indeed any mention of LD
at all:
#define LDFUNC_DECLARATIONS
#define PL_copy_term_ref(from) LDFUNC(PL_copy_term_ref, from)
term_t PL_copy_term_ref(term_t from);
#undef LDFUNC_DECLARATIONS
Or you can put the macro DECL_LD
inside the function parentheses, right at the start before the first argument (if there is one), with no comma. It’s basically just like ARG_LD
, with two exceptions: it goes at the beginning, not the end, and you don’t have to vary ARG_LD
vs ARG1_LD
.
#define PL_copy_term_ref(from) LDFUNC(PL_copy_term_ref, from)
term_t PL_copy_term_ref(DECL_LD term_t from);
term_t
PL_copy_term_ref(DECL_LD term_t from)
{ /* code goes here */ }
As you can see, it works for prototypes or for definitions, and semantically it has exactly the same effect as the LDFUNC_DECLARATIONS
method.
Point of interest: I have changed from passing it in the last argument to passing it in the first argument. It’ll probably save a few cycles here and there, in the case that one LD-using function calls exactly one other LD-using function and doesn’t need to use LD on its own, because (for any architecture with a register-passing call convention) the argument will already be in the proper register on function arrival, since it’s always in the same position. It also makes vararg functions easier to deal with, since you always know which argument is in the last named position
API_STUB
, in contrast, is someone easier to understand. It’s used when defining libswipl
API functions that need to use LD
, and the syntax (with an LDFUNC
definition and prototype for context) looks as follows:
#define PL_copy_term_ref(from) LDFUNC(PL_copy_term_ref, from)
term_t PL_copy_term_ref(DECL_LD term_t from);
API_STUB(term_t)
(PL_copy_term_ref)(term_t from)
( return PL_copy_term_ref(from); )
API_STUB
provides two things: first, it avoids expanding the PL_copy_term_ref(from)
macro in the declaration, by enclosing the function name inside its own parentheses; that means the name won’t get mangled with a suffix. Second, it prepends a GET_LD
to the function body (which is why the function body is in parentheses, not braces), so you don’t have to worry about where your function-local LD
is coming from. The fact that the function body is in parentheses does mean we won’t get line number debug information for the function, but hopefully these stubs will all be simple and short anyway.
You’ll note that this function is using PL_copy_term_ref(from)
in its code; since API_STUB
protects against function name expansion, there’s no need for an #undef
/#define
pair to write the declaration. In point of fact, the API_STUB
macro isn’t even needed for that—C is perfectly happy to have a function name in parentheses in a function declaration, and that also stops the function-like macro from expanding. I really like having the function name in parentheses in this case, actually, because it’s a hint that “something weird is going on with the function declaration here”, which should hopefully make it look a little bit less like an infinite self-recursion at first glance.
This has been a lot! Hopefully for the one or two people this actually impacts, it makes enough sense. You can see an initial commit, making changes to pl-fli.c
and pl-fli.h
:
And obviously, I’m interested to hear any thoughts folks have!