Rounding transcendentals broken on MacOS (Intel)

Just letting you know I’m completely out of my element here, so this won’t be quick.

I’ve started by trying to re-establish the ability to rebuild swipl from source using cmake, so:

  1. Xcode commandline tools 11.3.1. (for Mojave)
  2. cmake 3.22.3

Cloned the swipl-dev repository and got as far as:

$ cmake -G Ninja ..
CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!
See also "/Volumes/Mac Pro HD/Developer/swipl-devel/swipl-devel-master/build/CMakeFiles/CMakeOutput.log".

Appears some config is missing; suggestions?

The various routes for building SWI-Prolog on MacOS are described at Building SWI-Prolog on MacOSX

So started back at square 1, and it initially failed die to missing packages:

-- Configuring SWI-Prolog-8.5.10
-- Using Macports packages from /opt/local
-- Check floating point format
-- Check floating point format - IEEE-754, little endian
-- The following SWI-Prolog packages are disabled because the required sources are not installed:
chr clib clpqr inclpr cpp http mqi ltx2htm nlp paxos redis stomp PDT pengines pldoc plunit protobufs RDF semweb sgml table utf8proc zlib archive odbc cql bdb pcre yaml jpl ssl tipc swipl-win xpce libedit readline

...

But after a few attempts this now seems to have resolved itself; not sure how that happened.The cmake step now completes (after setting an install directory that doesn’t require privledges and turning doc production off) but several “not found”, e.g.,

-- Looking for fpsetmask
-- Looking for fpsetmask - not found

and “Failed”, e.g.:

-- Performing Test HAVE_GETTID_SYSCALL
-- Performing Test HAVE_GETTID_SYSCALL - Failed

I assume these are OK, i.e., needed to sort out platform differences. Executing the ninja step completed with a few warnings compiling pl-setup.c and pl-thread.c.

Running ctest -j 8 pretty much all failed. Looking at the test log, it appears that library(plunit) is missing. I’m only building from swipl-devel so I assume that’s to be expected (see comment on initial cmake above).

Installing it my $HOME directory, ~/bin/swipl now seems get me to the top level prompt in terminal mode, so maybe this is all I need to proceed with testing the changes required to add crlibm?

I have the impression you did not clone the git submodules. The first step is

git clone https://github.com/SWI-Prolog/swipl-devel.git
cd swipl-devel
git submodule update --init

Then

mkdir build
cd build
cmake -G Ninja ..
ninja

Given all dependencies installed that should all proceed with very few or no warnings (depending on the C compiler version. The policy is to ensure the compilation is silent on the most recent gcc and clang versions). Sure, many of the tests by cmake will say “no” or “failed”, etc. That it because it tries to find stuff that happens to be present in other systems.

All tests should succeed. At the moment there is is a problem with the http:proxy test on MacOS that sometimes fails, probably due to some timing issue with releasing and reusing network ports.

Nowadays, it easier to just do the initial clone by git clone --recurse -j10. Similarly, git pull --recurse -j10 for updating.

To be honest, I’m no longer sure sure what I did. Starting over with a new clone as per your instructions, build was OK and just one test failed:

17/76 Test #14: swipl:save .......................***Failed    2.11 sec

from the test log:

----------------------------------------------------------
Running scripts from save 
ERROR: /Volumes/Mac Pro HD/Developer/swipl-devel/untitledfolder/swipl-devel/src/Tests/save/test_saved_states.pl:222:
	test true: received error: Process "/Volumes/Mac Pro HD/Developer/swipl-devel/untitledfolder/swipl-devel/build/src/test_state_1_33724.exe": exit status: 126
ERROR: /Volumes/Mac Pro HD/Developer/swipl-devel/untitledfolder/swipl-devel/src/Tests/save/test_saved_states.pl:229:
	test argv: received error: Process "/Volumes/Mac Pro HD/Developer/swipl-devel/untitledfolder/swipl-devel/build/src/test_state_2_33724.exe": exit status: 126
ERROR: /Volumes/Mac Pro HD/Developer/swipl-devel/untitledfolder/swipl-devel/src/Tests/save/test_saved_states.pl:236:
	test true: received error: Process "/Volumes/Mac Pro HD/Developer/swipl-devel/untitledfolder/swipl-devel/build/src/test_state_3_33724.exe": exit status: 126
Script /Volumes/Mac Pro HD/Developer/swipl-devel/untitledfolder/swipl-devel/src/Tests/save/test_saved_states.pl failed
 done
*** 1 tests failed ***
<end of output>
Test time =   2.11 sec
----------------------------------------------------------
Test Failed.
"swipl:save" end time: Apr 08 13:57 EDT
"swipl:save" time elapsed: 00:00:02
----------------------------------------------------------

I’ve now built and tested twice to convince myself it’s repeatable so on to the next step. I intend to use the Macports version of crlibm so I don’t go down that rabbit-hole just yet.

At this point I’m still crawling; maybe some day I’ll try walking upright.

Some progress and a few unexpected results:

1.Installed crlibm from MacPorts; verified that crlibm.h was in opt/local/includes and libcrlibm.a in opt/local/lib/.

  1. Added the following to cmake/Config.cmake:
if(USE_GMP)
  check_include_file(crlibm.h HAVE_CRLIBM_H)
  check_library_exists(crlibm	crlibm_init "" HAVE_CRLIBM)
endif()
if(HAVE_CRLIBM)
  set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} crlibm)
endif()

so using the USE_GMP flag as an enabler for adding crlibm to the build (both LGPL). However cmake can’t find the files in MacPorts even though it says it’s using the MacPorts config:

$ cmake -DCMAKE_INSTALL_PREFIX=$HOME -G Ninja ..
-- Configuring SWI-Prolog-8.5.10
-- Using Macports packages from /opt/local
-- Looking for crlibm.h
-- Looking for crlibm.h - not found
-- Looking for crlibm_init in crlibm
-- Looking for crlibm_init in crlibm - not found
-- Could NOT find Qt5Widgets (missing: Qt5Widgets_DIR)
-- Configuring done
-- Generating done
-- Build files have been written to: /Volumes/Mac Pro HD/Developer/swipl-devel/SWIP_crlibm/swipl-devel/build

Then, as a workaround, added copies of the relevant files to /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr. and it now works (sample from stdout):

-- Looking for crlibm.h
-- Looking for crlibm.h - found
-- Looking for crlibm_init in crlibm
-- Looking for crlibm_init in crlibm - found
  1. Make cmake flags available to C preprocessor in src/config.h.cmake by adding:
#cmakedefine HAVE_CRLIBM_H @HAVE_CRLIBM_H@
#cmakedefine HAVE_CRLIBM @HAVE_CRLIBM@
  1. Define an unmodifiable environment flag float_correct that just reflects the value of HAVE_CRLIBM to verify the above works. Required adding atom definition to ATOMS and pl-arith.c, i.e.,conditional #include <crlibm.h> and initArith function now has:
#ifdef HAVE_CRLIBM
  setPrologFlag("float_correct",  FT_ATOM, "true");
#else
  setPrologFlag("float_correct",  FT_ATOM, "false");
#endif 

After rebuilding:

?- current_prolog_flag(float_correct,CR).
CR = true.

So far, so good.

  1. Add a call to crlibm_init() to initArith. Linking now fails:
Undefined symbols for architecture x86_64:
  "_crlibm_init", referenced from:
      _initArith in pl-arith.c.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.

For whatever reason (missing config, incompatible library file, ??) the linker can’t resolve crlibm_init defined in CRlibm.h as:

extern unsigned long long crlibm_init(void);

Any suggestions to how to get past this?

Is there a swipl_plugin(…) in your CMakeLists.txt?

If so, my guess is that you need to add C_LIBS to it.

For an example, see packages/pcre/CMakeLists.txt and packages/pcre/cmake/FindPCRE.cmake

Hard to say. Needing to copy the files to the system library is already wrong. Typically means checking the exact steps (ninja -v) to see whether it really links
the library. Could be related to the fact this is a static lib (.a). You’ll in the end need a shared lib (.dylib) for license requirements. Then verify the handling is consistent with libgmp as this should come from the same location. There are also log files for CMake that might give clues.

Running ninja -v doesn’t seem to add much information. What I do know:

  1. && /Library/Developer/CommandLineTools/usr/bin/cc -O2 -g -DNDEBUG -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk -mmacosx-version-min=10.14 -dynamiclib -Wl,-headerpad_max_install_names -compatibility_version 8.0.0 -current_version 8.5.10 ... /opt/local/lib/libgmp.dylib /opt/local/lib/libz.dylib -ldl -lm -framework CoreFoundation && :

  2. Input files to the command include /opt/local/lib/libgmp.dylib

  3. /opt/local/lib/ contains libcrlibm.a but libcrlibm.a is not listed in the command input.

I assume a good place to start is 3., i.e., why did cmake not generate a link command which includes the crlibm library.

Maybe it all boils down to the fact that this is a static library, but that’s not clear. And maybe a dynamic library is required at some point - maybe sooner than later - but at the moment I have no clue how to do that.

You seem to be assuming facts not in evidence, i.e., that I know what I’m doing.

Thanks for the suggestion, but I’m not sure how much of this applies. I’m not building a plugin or a package so I don’t have a separate CMakeLists.txt. I’m trying to (conditionally) add a library to the core and use it to implement arithmetic functions.

That said, there is probably a list somewhere that I need to include the new library but I haven’t yet found it in the dozens of files and thousands of lines of cmake source that are used to build the core.

Guess so. To me, the ninja -v is quite useful: it tells is the library is not linked. If it was, we’d have to look into what is wrong with it. Now we have to find why it isn’t linked. Note that most variables are in CMakeCache.txt in the build directory. So, a grep -i crlibm CMakeCache.txt is a good start.

Putting some message(“some text ${myvar}”) in the cmake files can also give insights. Indeed, you do not need to plugin stuff. That is for extensions only.

Looking at CMakeCache.txt, the only references to crlibm are:

//Have library crlibm
HAVE_CRLIBM:INTERNAL=1
//Have include crlibm.h
HAVE_CRLIBM_H:INTERNAL=1

which I might expect given the changes I made to cmake/Config.cmake detailed in an earlier email. I believe I’ve verified that the flags are working properly since compiling seems to be fine and I can define a “float_correct” environment flag based on them.

Switching tacks, if I look for references to libgmp in CMakeCache.txt I find:

//Path to a file.
GMP_INCLUDE_DIRS:PATH=/opt/local/include

//Path to a library.
GMP_LIBRARIES:FILEPATH=/opt/local/lib/libgmp.dylib

GMP_LIBRARIES_DIR:FILEPATH=/opt/local/lib

also:

//Dependencies for the target
libswipl_LIB_DEPENDS:STATIC=general;/opt/local/lib/libcurses.dylib;general;/opt/local/lib/libform.dylib;general;/opt/local/lib/libgmp.dylib;general;/opt/local/lib/libz.dylib;general;dl;general;m;general;-framework CoreFoundation;

and

//Details about finding GMP
FIND_PACKAGE_MESSAGE_DETAILS_GMP:INTERNAL=[/opt/local/lib/libgmp.dylib][/opt/local/lib][/opt/local/include][v()]

The “Dependancies” reference would seem to be the most promising. I then did a multifile search in the cmake directory for libgmp and found the following references:

  1. cmake/cross/linux_i386.cmake:14: # libdb-dev:i386 libedit-dev:i386 libgmp-dev:i386 \

  2. cmake/FindGMP.cmake:15: set (GMP_LIBRARIES ${GMP_ROOT}/libgmp.so)

  3. cmake/FindGMP.cmake:60: NAMES gmp libgmp

  4. cmake/port/Windows.cmake:45: list(APPEND WIN32_DLL_PATTERNS "libgmp-*.dll")

This didn’t seem to help much, so looking at the other cases in Config.cmake, I expanded the crlibm additions to the following:

# CRLIBM conditional on USE_GMP
if(USE_GMP)
  check_include_file(crlibm.h HAVE_CRLIBM_H)
  check_library_exists(crlibm	crlibm_init "" HAVE_CRLIBM)
endif()

if(HAVE_CRLIBM_H)
  set(CMAKE_EXTRA_INCLUDE_FILES ${CMAKE_EXTRA_INCLUDE_FILES} crlibm.h)
endif()
if(HAVE_CRLIBM)
  set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} crlibm)
endif()

but no effective change to the end result (link failure, no libcrlibm in the list).

So I’ll keep poking but I’m not optimistic. Should it really be this hard?

So for any who have been following this, the missing piece seems to be the following in src/CMakeLists.txt

if(HAVE_CRLIBM)
  set(LIBSWIPL_LIBRARIES ${LIBSWIPL_LIBRARIES} crlibm)
endif()

In summary, to add a new library to the core and define HAVE_ flags for the C preprocessor affects the following files:

cmake/Config.cmake
src/CMakeLists.txt
src/config.h.cmake

As mentioned earlier, I also had to copy the crlibm header and library files to /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr on my system because cmake couldn’t find them where MacPorts put them, so there’s likely something else that I missed. But good enough for now.

packages/cmake/PrologPackage.cmake has some include_directories directives – perhaps this is the place to add the appropriate MacPorts include directory? I would assume it’s safe to just do a test for MacOS because the compilers ignore “-I” directories that don’t exist; but probably cleaner to also have a test for the directory existing.

The MacOS paths for dependencies are setup in cmake/ports/Darwin.cmake by extending CMAKE_LIBRARY_PATH and CMAKE_INCLUDE_PATH. On the Mac it is not safe to just add paths. Libraries and includes may come from /usr, the xCode SDK directories, Macports, Homebrew or other locally built dependencies. Often the same dependency appears in multiple places and one should be careful to make sure include files and libraries match as well as to avoid getting outdated versions or partial ports from MacOS itself as opposed to the generally up-to-date Macports and Homebrew versions.

One needs to setup a package search along the lines of e.g., cmake/FindGMP.cmake. Done properly, that should all work fine. If such a file is not part of CMake you can search the internet or create it yourself …

I appreciate that the build process for SWI-Prolog is complex - lots of dependancies across many platforms. But cmake is a quagmire best left to the experts and I certainly don’t count myself among them.

I installed the library of interest using MacPorts, like everything else that was in the list of build requirements. It appears to have put the required header and library in opt/local/ like everything else installed with MacPorts. cmake says it’s using the MacPorts configuration. I didn’t add anything to any path variables that I’m aware of. And yet it failed to find the 2 files of interest - it seemed to find everything else it needed.

My workaround is to make additional copies of the files in the Xcode sdk directories. That appears to have solved my problem. Why that should be the case, I have no idea. I may have done something stupid, or it may be a quirk that’s confined to my particular configuration. But I’ve spent the better part of a week trying to do what I thought was a simple task and I’m not prepared to spend any more time on it without some additional justification.

I’ve finished a first cut at this so it’s probably a good time to review it before I invest a lot of time in detailed testing. For comment:

  1. Use of crlibm rather than libm for elementary functions will be a build time option. That option is conditional on the USE_GMP flag being true and the presence of the requisite header and library files in a place where they can be found. From cmake/Config.cmake:
if(USE_GMP)
  check_include_file(crlibm.h HAVE_CRLIBM_H)
  check_library_exists(crlibm	crlibm_init "" HAVE_CRLIBM)
endif()

Everything else is predicated on flags HAVE_CRLIBM_H and HAVE_CRLIBM . I debated whether this warrants a new flag rather than reusing USE_GMP, but since they’re both LGPL libraries and are both concerned with “enhanced” arithmetic, maybe the single flag will suffice. If you don’t want GMP, you probably don’t want crlibm either. And if you want GMP, adding crlibm is unlikely to make any difference to most users.

  1. I added a non-modifiable environment flag, float_correct which is true if HAVE_CRLIBM is defined and otherwise false. I think this will be helpful for enabling test programs and I’ll probably make use of it in implementing relational interval arithmetic.

  2. In pl-arith.c, inclusion of the crlibm header is conditional on the flag HAVE_CRLIBM_H.

  3. Again in pl-arith.c, correctly rounded versions of the functions: exp, log, log10, sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, **, ^ will be used conditional on flag HAVE_CRLIBM. Standard libm versions will continue to be used for functions: asinh, acosh, atanh, erf, erfc, lgamma.
    The error for correctly rounded function should be less than 0.5 ULP with the exception of ** and ^ with rounding modes to_positive, to-negative or to_zero where the error should be less than 1.5 ULP. (crlibm does not support rounding modes other than to_nearest for function pow.) The error for non-correcly rounded functions is unspecified, but probably on the order of 1ULP most of the time. Results from correctly rounded functions should be consistent across all platforms assuming C99 and IEEE754 compliance.

I think this scheme meets two major objectives: a) it’s pretty simple to configure a build to optionally include the crlibm library, and b) the average user likely won’t notice any difference, barring the removal of oustanding bugs in platform specific libm's like the one that spawned this thread. However for testers and those who may be interested in correctly rounded results (perhaps I’m the only one) I think this is a significant step forward.

Outstanding issues:

  1. As delivered, either from the githib project archive or from MacPorts, the result is a static library (libcrlibm.a). I have rebuilt the library from source so I don’t anticipate any issues in building a dynamic library but, as I keep saying, this isn’t my area of expertise. And obviously my build experience is pretty limited when you consider the many platforms and compilers in the SWIP picture.

  2. There is justifable concern that this an “orphaned” library and hasn’t been updated in many years. In this particular case, this isn’t as much of a worry for me. It’s not as if requirements are changing or new functionality is being added/requested. I’d actually be more concerned if there were a lot of recent changes. The fact that it seems to be used in an active Python project (py_intervals) provides some re-assurance of stability. Finally the ability to back it out of the build process with few people being any the wiser in the event of some catastrophic failure should minimize any risk.

These are the main ones that I can see, but my horizons are pretty limited. I still have plenty of testing to do before I would consider this ready for prime time, but would appreciate any feedback on progress so far.

Good to hear you made a lot of progress!

The details of which flags control whether all this is enabled are a minor issue. For the Prolog part I’m mostly interested to avoid pl-arith.c getting yet more unreadable by another layer of #ifdefs. If that is avoided (by effectively redefining sin(), etc) I’m happy.

I am still pretty worried about the implication wrt. portability and maintainability. As is, I’ve downloaded the library and build it using the GNU autotools. That indeed creates libcrlibm.a. There is no support for building a shared object. There is a CMake spec. With some quick fixes that runs through CMake, but the configuration part seems pretty incomplete. This implies we must port the stuff from configure.ac to cmake. That is surely doable. Probably that is wise to do. For one thing, static libs cannot be included into the libswipl.so shared object when not compiled as position independent code (-fPIC).

The crlibm source also points at two libraries to compare to. One by IBM and one by SUN. Both are on GitHub. The IBM one is GPL, making our license issue worse. The SUN one has a liberal license except for the curious clause that we are not allowed to use it in nuclear facilities :slight_smile: The SUN one is at GitHub - simonbyrne/libmcr: Sun libmcr (correctly rounded libm). It seems to require almost no configuration, which makes it easy to write a CMake spec for it. Is that worth considering? A serious downside is that the tests appear to be missing. They are referred to in the Makefile, but there is no tests directory in the source :frowning:

Another way to think about this is to provide an array with function pointers that we allow to be modified through the foreign language interface. So, you can define your own function and do e.g.

PL_set_function(AR_SIN, my_sin).

And then the code in pl-arith.c simply does e.g., f = functions[AR_SIN](x). With this we can put this stuff in a normal package or external add-on. That at least makes the license implications dynamic and allows for alternative libraries.

I obviously haven’t helped this situation but I don’t think it’s too bad. I’ve largely confined the #ifdef by defining a new macro:

#ifdef HAVE_CRLIBM

#define CALL_ELEM_FUNCTION1(func, n, r) \
  int roundMode = fegetround(); \
  if ( roundMode != FE_TONEAREST ) fesetround(FE_TONEAREST); \
  switch( roundMode ) {  \
	case FE_TONEAREST  : r->value.f = func##_rn(n->value.f); break; \
	case FE_UPWARD     : r->value.f = func##_ru(n->value.f); break; \
	case FE_DOWNWARD   : r->value.f = func##_rd(n->value.f); break; \
	case FE_TOWARDZERO : r->value.f = func##_rz(n->value.f); break; \
  } ;\
  if ( roundMode != FE_TONEAREST ) fesetround(roundMode); 

#else /* HAVE_CRLIBM */

#define CALL_ELEM_FUNCTION1(func, n, r) \
  r->value.f = func(n->value.f);

#endif

So that everywhere you had something like r->value.f = func(n->value.f); before, replace it with CALL_ELEM_FUNCTION1(func, n, r);. It turns out much of this is through macro UNAIRY_FLOAT_FUNCTION(name, op) but it gets a little more complicated again because not all such functions, e.g., erf and lgamma, are supported by crlibm. So you need to define an additional UNAIRY_CR_FLOAT_FUNCTION for supported functions (using CALL_ELEM_FUNCTION1) and retain the current one for unsupported ones.

But you’re still not quite out of the woods because, for the same reason (not directly supported), atan2 and tanh require some customization.

And finally, only a correctly rounded version of ‘to_nearest’ for pow is implemented, so that’s handled via a new CALL_POW_FUNCTION2 macro and some conditional code in the private function cond_minus_pow.

So in all there’s 4 “#ifdef HAVE_CRLIBM” to cover the elementary functions and 2 for the float_correct flag. There may be better solutions but that’s all I could come up with given my elementary C programming skills.

I think that’s basically what I did (autoconf and automake ?) but I really didn’t understand what I was doing.

So what I’m building is a functioning executable but doesn’t contain a usable libswipl.so if I’m understanding you correctly.

Producing a configurable, dynamic library appears to be more complicated than I had hoped, but not sure what I can contribute on that front. I’ll do some more research on the Sun library to see if that’s a realistic alternative.

I was definitely not thinking that far out of the box; that would be even a bigger stretch for me.