C API, switch to `bool`?

The next C standard will add true, false and bool as keywords to the language. There is already stdbool.h which provides this type and constant. As is, the internal source uses typedef int bool and uses macros true() and false(). To make all that work some magical #undef and #define was in use.

I have now done a global replace to get rid of the true() and false() macro conflict. That is purely internal and fine.

Most of the C API functions are defined as int, but can only return 0 or 1, made available by SWI-Prolog.h as FALSE and TRUE. The PL_cvt_*() group was internally implemented with type bool. I changed the types in SWI-Prolog.h from int to bool for these and included stdbool.h into SWI-Prolog.h.

I consider changing all the others really boolean functions to bool as well. I think this maintains binary compatibility as C return promotes bool to int (right?). Does anyone see any problems with this?

Note that I so not propose to change PL_get_bool(term_t t, int *val) as that would break compatibility.

Opinions?

1 Like

+1

When I look up a PL_*() function, I first look in SWI-Prolog.h (or SWI-Stream.h). One of my biggest concerns is whether the function can return a value other than true or false, which also requires looking in the documentation; using true and `false saves me one check (also, there have been a few cases where there was no documentation about the return code, so I then had to look at the source code).

[We might need to coordinate the change with SWI-cpp2.h, although that’s probably not needed.]

PS: One other thing that it’d be nice to know from just looking at the .h file is whether the return code indicates success/failure or success/error – that is, should I call PL_exception() on failure. (SWI-cpp2-plx.h tries to do this in a consistent manner – and throws a C++ exception if there PL_exception() returns true, but I suspect that I didn’t get everything quite right.)
(There are ~90 functions where false means error, ~60 where false means failure (or error); and ~170 where the return value is something else.)

That seems to require no changes. My current plan is to update the core, but keep the packages as is where possible. We’ll update these with 9.4/9.5.

The main question is whether or not this breaks binary compatibility?

I did a little experiment, processing both of these files with gcc -O0 -S --std=c2x -Wall -Wextra:

File b1.c
#include <stdbool.h>

__attribute__ ((noinline))
bool f1(bool flag, int value) {
  if (flag) {
    return value != 1;
  }
  return value == 0;
}

__attribute__ ((noinline))
bool f2(int value) {
  return f1(true, value);
}

extern bool f3(bool flag, int value);

bool f4(int value) {
  return f3(true, value);
}
File b2.c
__attribute__ ((noinline))
int f1(int flag, int value) {
  if (flag) {
    return value != 1;
  }
  return value == 0;
}

__attribute__ ((noinline))
int f2(int value) {
  return f1(1, value);
}

extern int f3(int flag, int value);

int f4(int value) {
  return f3(1, value);
}

The code generated for function f1() was different in both (I don’t know enough x86 to say whether the differences were meaningful); but the code for calling f1() and f3() was the same in both (you may wish to confirm this with other compilers and other optimisation levels).
I’m guessing (caveat: I retired from being a C language lawyer over 3 decades ago) that there’s a backwards-compatibility rule in C that requires an argument to be at least one “word” (defined by the size of int); if my guess is correct, then changing from int to bool would not break binary compatibility.

1 Like

My uninformed opinion is that this sounds good but I guess I am missing the point.

I have some questions though. For writing foreign code, does it mean that foreign_t foo(...) can return true and return false instead of TRUE and FALSE?

  • Do I need to use an explicit compiler flag to use C23?
  • Do I need to checkout a branch of SWI-Prolog to include the new stdbool.h? The only open branch I see is “align-with-spaces”

Or did I misunderstand this?

Yes. TRUE is defined as true (and FALSE and false). TRUE and FALSE should be deprecated.

No. C23 is not yet even final. Fortunately, C11 introduced _Bool and <stdbool.h>, providing the same API, just true, false and bool are not keywords. C23 keeps providing <stdbool.h> (mostly empty). SWI-Prolog.h will include <stdbool.h>.

It is still work in progress. Don’t be too impatient :slight_smile:

1 Like

Ok, pushed to swipl-devel. All seems to compile cleanly using gcc and clang. All tests pass. There is some risk for regression as bool used to map to int and now maps to char. This may notably affect PL_scan_options() which is used in the C code to process options for foreign predicates. Docs are updated (using a Prolog script :slight_smile: ) and a new section was added to discuss the various issues. @peter.ludemann is likely to have comments :slight_smile: See DOC: Explain issues and features of the C-API Boolean functions. · SWI-Prolog/swipl-devel@bb4188f · GitHub from line 450.

Please test.

Ideally a large number of internal functions should go through the same change. Automatic analysis for possible return values would be enough (if the return consist of boolean expressions or functions returning bool the function should be bool). Is anyone aware of tooling to figure that out?

The file SWI-cpp2-plx.h attempts to classify the PL_*() functions, as follows:

  1. true/false – e.g., PL_is_variable()
  2. false means an error – e.g., PL_put_atom()
  3. false means failure or error – e.g., PL_unify_atom()
  4. returns a value or “false” – e.g., PL_open_query()
  5. returns some other value – e.g., PL_next_solution() with “extended status”
  6. returns void

It would be nice if this information were in a canonical place, such as SWI-Prolog.h or foreign.doc (or both). Perhaps we could define three types that all map to bool: bool_asis_t, bool_exce_t, bool_t.
Case 5 is now implicitly marked by not returning bool (although it’s not always clear that there’s a “false” value, such as (qid_t)0).

(To make things friendly for C++ programmers, the C++ API checks for Prolog errors and throws a C++ exception on error – that is, for cases 2 and 3, it calls PL_exception() if the return value is false.)

I’m not against a formal annotation, although a lot is already covered by the naming convention, surely combined with the new bool. There are two more categories, that is bool functions that always return true such as PL_put_term(), PL_put_atom(); all PL_put_*() that do not need to do any allocation. They are of type bool such that we can do

return ( (t=PL_new_term_ref() &&
            PL_put_atom(t, ATOM_hello) &&
             ...);

If these functions were void this gets ugly C code :frowning: The other category are the exception raising functions, that always return false, so we can write

  if ( ... )
    return PL_domain_error("my_domain", t);

I’d prefer annotations similar to the current WUNUSED though. At least modern C compilers handle these things using attributes rather than types. The macros can be defined to nothing, e.g. #define PL_MAY_THROW. We could even pass the possible errors as argument to the macro.

C++ has the opposite: [[nodiscard]] goes with the type. But I’m fine with putting the annotation after the declaration – I just want to be sure that SWI-cpp2-plx.h matches SWI-Prolog.h everywhere – I’ve caught a few mistakes (that I made) while writing C++ code; and it wouldn’t surprise me if there are more.

So @jan – no objections if I make a PR that annotates the functions that return bool in SWI-Prolog.h?

But I have to look up the naming convention (for some reason, how PL_put_*() works isn’t intuitive to me).

The equivalent in C++ would be:

PlTerm t(ATOM_hello);

and it would throw a C++ exception PlExceptionFail, which is caught at the enclosing predicate and the error is then passed back to Prolog.

So, in most cases, C++ code doesn’t benefit from the “always return true” or “always return false” (the equivalent of if ( ... ) return PL_domain_error(...); is PlCheckFail(...); or if ( ... ) throw PlDomainError(...);)

I Can’t find a definitive answer to that. Quite surely, __attribute__((warn_unused_result)) is a attribute of the function rather than the type. It doesn’t really matter, I guess.

I used a script to make sure the new bool return value is properly represented in the documentation :slight_smile: So, yes, I’m in favour of a formal annotation that can be used to verify consistency and possibly simply generate e.g., SWI-cpp2-plx.h

On the other hand, I would be ok accepting the naming convention as part of the formal specification. PL_put_*() comes from Quintus :slight_smile: It assigns the handle some Prolog value, replacing the value it may have before the call.

True, at least not for the user perspective. The reduced boilerplate is the big advantage of the C++ wrapper. The downside is that you need to write C++ and that tends to break down every 6 months while C code tends to last for 30 years :slight_smile: (sorry, could not resist) The wrapper can use these rules to simplify the code. So, after a PL_is_() or PL_get_(), there is no need calling PL_exception(0).

Note that another part of the usage rules is how term_t handles may be used and how they are affected by calls. The docs use the mode-derived notation +term for input and -term for output (meaning the function will use PL_put_() on it). Finally, ?term means the function unifies to the term. Furthermore, term_t passes as predicate arguments may not be used in PL_put_() (this is since recently checked at runtime). Should we also add the “mode” annotation to SWI-Prolog.h? If so, how?

I suppose I should have said “[[nodiscard]] goes with the return type of the function, e.g. [[nodiscard]] int foo()” rather than afterwards “int fool() WUNUSED”. One could argue that the C++ way makes for more visual clutter. :slight_smile: On the other hand, the C++ way could be used to mark the meaning of the result, e.g., FailOnly<bool>, FailOrError<bool>, AlwaysTrue<bool>, etc. (This is a fairly minor issue, and probably not worth further discussion.)

I was thinking of doing this by hand, but if there’s a script that helps automate it, please send it to me, even if it’s user-unfriendly (it might give me ideas as to how to automate).
Functions not returning bool would continue to be done in SWI-cpp2-plx.h by hand, I suppose.

I can easily change the PL_put_*() functions in SWI-cpp2-plx.h to not check the return code. However, I noticed that most of the PL_put_*() functions are marked WUNUSED, which implies that they can get an error. For PL_is_*() and PL_get_*(), the C++ API already has no check for exceptions.

For the C++ API, these should be methods of PlTerm (typically const, even if mode is -). In some cases, the method or function returns a PlTerm and throws a C++ exception if there’s an error. The ?term annotations are typically for the unification methods, and these return a bool that is checked for error (and throws a C++ exception).

Note that for the C++ API, if the programmer wishes to avoid the checks for errors, they can always use the PL_*() functions (typically with the PlTerm::unwrap() method to access the term_t or PlAtom::unwrap() for atom_t, etc.)

In my experience, it usually takes a few years before C++ code breaks on its own. :slight_smile: That’s assuming the compiler got the complicated semantics correct, of course. :slight_smile:

But, yes, the big advantage of using the C++ API is the reduction in boilerplate code and a certain amount of memory/resource management using “smart pointers” (which are only partially compatible with Prolog’s memory management). Also, the API tries to ensure that checks for errors are done everywhere without cluttering up the code, although the checks might be in places that aren’t strictly needed.

1 Like

So far, I used grep and sed to find all bool functions and a bit of Prolog to update man/foreign.doc. It shouldn’t be too hard to write a DCG that gets the functions and their types from SWI-Prolog.h and create a little Prolog database. From there we can update docs and generate interfaces for other languages than C. If anyone contributes that, I’m happy to add it to the library.

We can also use the ffi pack library to get to the C properties, but that might be a bit of an overkill.

You must check the return code, but you know there is an exception in case it returns false. Of course, that helps little. It is an exception scenario anyway. Note that not all PL_put_*() functions have WUNUSED; only those that allocate something and (thus) may fail. The others are guaranteed to return true and are only bool rather than void to allow connecting them using && (C’s way to avoid boilerplate :slight_smile: )

I see no point in doing this for C++ – the work has already been done (mostly using some simple emacs macros). What other languages might want a foreign language interface to SWI-Prolog?

What I’m concerned about are the semantics of the various PL_*_() functions – did I wrap them appropriate in the C++ API (that is, use the return value as-is, for detecting failure, or checking for error)? I don’t see an obvious way of automating this … I would suggest adopting some kind of standard mark-up in foreign.doc and a related mark-up in SWI-Prolog.h – this could then be used to verify SWI-cpp2-plx.h. (The semantics is mostly: “should the return code be checked for a Prolog error, and a C++ exception thrown?”)

(BTW, it occurs to me that I might have made a design mistake with SWI-cpp2-plx.h; instead of uniformly using Plx_*() names, I could have distinguished the type of return code in a naming convention, such as Plx_asis_*(), Plx_ex_*(), etc. These names would be a bit cumbersome, but they’re mostly only used in SWI-cpp2.h, so that wouldn’t be a problem. It might not be too late to change this.)

Surely the docs. It is not uncommon that the header and docs are out of sync. Possibly the pyswipl Python binding on Python ctype interface. I do not know how exactly the Rust binding is kep up-to-date. @maren?

We now have that for the big groups, i.e., PL_put_(), PL_is_(). etc. The other functions vary. It would probably be good to have an annotation for whether or not they can raise a Prolog exception. Note that as long as the calling thread has a Prolog engine it is always safe to call PL_exception(0) and act on the exception. Is there some existing practice for this type of annotation?

There seems to be a missing sentence – do you mean that the docs are the source of truth and that the headers sometimes get out of sync? If that’s the case, perhaps some kind of LaTeX macro that can be used to generate a Prolog file, and generate the .h files?
What is “pyswipl”? Did you mean “swipy”?

As for annotation, there’s Java has throws and C++ has a nothrow; but Java doesn’t require specifying exceptions for RuntimeException , Error , and their subclasses; and these would correspond to things like ResourceError in SWI-Prolog. (Java’s requirements of specifying exceptions was and is controversial (IIRC, C# takes the opposite approach); I rather like it because the compiler can catch certain kinds of errors but apparently many people don’t like verifying that their code is robust.)

rust bindings have a low-level component that gets auto-generated from the header files of the specific SWI-Prolog version you’re building against. This is done using bindgen. A high-level component then wraps that into an ideomatic rust API.

Changes to the header files sometimes lead to build warnings or failures, even if they would be C-compatible otherwise. This is not a big deal and easily fixed.

For the rust bindings, there won’t be much use for a prolog database with definitions.

Specifically for changing ints to bools in various functions, I don’t really know what bindgen will make of that, but I suspect it’ll mirror the type and generate an fli api that uses bools. That in turn will lead to build failures for the high-level wrapper (which now expects ints). But that’s an easy fix.

1 Like

I was thinking the other way around. SWI-Prolog.h has the truth and the docs should follow. Sometimes they don’t.

Sorry, meant pyswip. It allows calling Prolog from Python based on the Python ctypes interface.

So, we could do PL_THROWS(…), where … is an ,-separated list of exceptions. E.g., PL_THROWS(type_error,resource_error), or PL_THROWS(type_error,...) or simply PL_THROWS(...) to indicate it can throw just about anything. I’d be happy with that.

I’d like to distinguish between “returns success/fail/error” and “returns success/error” (also, “returns success/fail”, “returns true”, “returns false”). But I can’t think of a compact way of saying this … PL_THROWS_ONLY, PL_THROWS_OR_RESULT are the best I can do (also, PL_RESULT, PL_RESULT_TRUE, PL_RESULT_FALSE).

Tracing the code to find all the possible errors can be non-trivial (I’ve tried doing that for a few functions), and I wonder whether it’s a good idea to do it for a subset of the functions because that might lead people to think that where it’s not documented it doesn’t happen. Some others are “obvious” – e.g.: PL_get_atom_ex() can give ERR_TYPE. In general, I don’t think it’s terribly useful to document which errors can happen because the usual thing is to simply pass the error back to Prolog.

Is this GitHub - yuce/pyswip: PySwip is a Python - SWI-Prolog bridge enabling to query SWI-Prolog in your Python programs. It features an (incomplete) SWI-Prolog foreign language interface, a utility class that makes it easy querying with Prolog and also a Pythonic interface. ?
Isn’t it obsolete, replaced by package/swipy?

Agreed.