Foreign Language Interface - handling Prolog exceptions in C

I’d like to use the is predicate from some C code in such a way that any generated exceptions get a sniff (not shown below) and then are optionally re-generated. Prototype C code (as a function) looks like:

static int is_eval(term_t result, term_t exp) {
  static predicate_t pred_is = 0;
  if (!pred_is) pred_is = PL_predicate("is",2,"system");  //initialize once
  term_t args0 = PL_new_term_refs(2);
  PL_put_term(args0  ,result);
  int res = PL_call_predicate(NULL,PL_Q_CATCH_EXCEPTION | PL_Q_EXT_STATUS,pred_is,args0);
  if (res == PL_S_EXCEPTION) 
    return PL_raise_exception(PL_exception(0));
    return res;

The no exception case seems to work as intended, but if an arithmetic exception is generated during evaluation, the result is success although nothing is bound to the result term.

Is this my bug?

Not sure, but the PL_Q_EXT_STATUS may only work for the PL_open_query()/PL_next_solutuon() interface. Anyway, simpler is probably to use
PL_Q_PASS_EXCEPTION and test for an exception using PL_exception(0) if rc is FALSE.

Thanks. For the record this does the job:

static int
is_eval(term_t result, term_t exp, int rmode) {
  static predicate_t pred_is = 0;
  if (!pred_is) pred_is = PL_predicate("is",2,"system");  //initialize once

  term_t args0 = PL_new_term_refs(2);
  PL_put_term(args0  ,result);

  int res = PL_call_predicate(NULL, PL_Q_PASS_EXCEPTION, pred_is, args0);
  if (!res) {
    term_t exc; 
    if ((exc=PL_exception(0))) res=PL_throw(exc);
  return res;

Be extremely careful with this. This performs a C longjmp(), discarding everything up to its handler and thus easily leaks resources or worse. Just returning FALSE should be enough as long as all the code in between keeps returning FALSE until we return FALSE back to the Prolog call that started this.

If you are not called from Prolog you’ll have to deal with the exception in C and discard it using PL_clear_exception().

I think I’m safe.

is_eval will only be called from a function registered as foreign (using PL_register_foreign) and it’s only use is as return is_eval(result,exp);

It is way safer to simply return with FALSE to your caller in that case … C longjmp() is a can of worms you better keep closed :slight_smile:

Do you mean:

int res = PL_call_predicate(NULL,PL_Q_PASS_EXCEPTION,pred_is,args0);
if (!res) {
	term_t exc; 
	if ((exc=PL_exception(0))) PL_throw(exc);
return res;

I don’t see the difference!? I mean as below. PL_Q_PASS_EXCEPTION keeps the exception in the environment. If you return with FALSE from the predicate, Prolog propagates the current exception. If you return with TRUE, this prints a warning. If you want to stop the exception from propagating, using PL_clear_exception().

mypred(term_t ...)
{ ...

  int res = PL_call_predicate(NULL,PL_Q_PASS_EXCEPTION,pred_is,args0);
  if (!res) {
	term_t exc; 
	if ((exc=PL_exception(0))) <hey, I got an exception>
  return res;

My confusion is whether anything has to be done when an exception is raised from inside the PL_call_predicate call, e.g., arithmetic overflow. It sounds like the answer is no, so the following would suffice:

return PL_call_predicate(NULL,PL_Q_PASS_EXCEPTION,pred_is,args0);

This would behave exactly like is from the perspective of the caller, i.e., succeeds if is succeeds, fails if is fails, and generates an exception if is does.

I have another little mystery. Calling is through this new foreign predicate seems to be much slower.

A baseline using call:

?- Exp=1.0+2.0,time((between(1,100000,_),call(R is Exp),fail)).
% 200,000 inferences, 0.032 CPU in 0.035 seconds (93% CPU, 6215813 Lips)

Now a call through the foreign predicate eval_dn:

 ?- Exp=1.0+2.0,time((between(1,100000,_),clpBNR:eval_dn(Exp,R),fail)).
 % 300,000 inferences, 0.222 CPU in 0.281 seconds (79% CPU, 1350092 Lips)

eval_dn avoids calling is if `Exp is just a float:

 ?- Exp=1.0,time((between(1,100000,_),clpBNR:eval_dn(Exp,R),fail)).
 % 200,000 inferences, 0.019 CPU in 0.019 seconds (100% CPU, 10639430 Lips)

So the calling mechanism to the foreign predicate seems very efficient, but it looks like calling is via eval_dn is about 5 times slower than through a call. Further the CPU% is atypical dropping below 80%.

The code comes from a shared object (.so) generated by swipl-ld and copied to swipl/library inside the SWI_Prolog (v8.1.19) Mac bundle.

From looking at the SWIP source, it appears that call uses the same code as PL_call_predicate (PL_open_query, …) so this has me baffled. Maybe it’s Mac specific?

Any thoughts?

Provided my interpretation is correct, some remarks. PL_call_predicate() (or mostly PL_open_query()) is quite expensive. It basically builds a new VM instance on top of the current one. This means it needs to push a dedicated query frame that saves enough information about the current state to restore and allow GC/SHIFT operations to deal transparently with the new environment. Next it pushes a dedicated top frame that ensures that other stack operations do not have to be aware they are working on the initial frame and finally it creates the call frame for the target predicate and initializes its arguments. If the predicate is done we have to tear all this stuff down again and restore the old context.

Call/1 is implemented by the VM instruction I_USERCALL0. As we do not need an isolated environment, this is as simple as creating a call frame from the goal and activate it. It is a bit more expensive than a normal call because we have to find the predicate at runtime (two hash lookups) and need to fill the arguments dynamically rather than relying on stuff prepared by the compiler.

I would be interested to see whether other Prolog systems give fundamentally better performant call backs from C (and provide GC/SHIFT; without these it gets a lot easier). One day we had Bart Demoen doing benchmarks on the less standard features such as exception handling. He never timed call-backs from C AFAIK.

[edit] If you have a convincing reason to call only is/2 from C we could of course consider a shortcut, e.g, PL_is() that call the expression evaluation right away. I have some trouble to see why you would want to use the whole Prolog machinery to evaluate an arithmetic expression though … Using libgmp directly from C is probably about as simple as creating the Prolog arguments.

I have avoided a discussion of the bigger picture as I didn’t want to distract from the question. But here goes:

I’m interested using intervals in a constraint system over the real domain. Intervals are just a pair of numeric values [Lower,Upper] representing a set of reals so Lower=<R=<Upper. To be mathematically sound, when arithmetic is performed on the upper (resp. lower) bound, floating point values have to rounded towards infinity (resp. -infinity).

I have a Prolog implementation of this rounding using epsilon but it’s obviously not optimal in performance and can perform more rounding than necessary leading to more conservative intervals.

C99 can control the rounding mode (see <fenv.h>), so I’d like to wrap any arithmetic calculations with rounding mode control, e.g.,

   int saveRM = fegetround();
   int res = PL_call_predicate(NULL, PL_Q_PASS_EXCEPTION, pred_is, args0);

It all seems to work but the hoped for performance gain (vs. using Prolog epsilon) is lost given the apparent cost of using PL_open_query.

Given the expression being evaluated is in the form of a Prolog term (not a simple number), wouldn’t using libgmp essentially entail replicating all the machinery in is (valueExpression)?

So yes, a PL_is function would be very nice. And it would also be quite helpful if it didn’t mess with IEEE non-numbers (infinities in particular), but that’s secondary.

Often the big picture matters.

For my understanding, you are happily in Prolog and now you want to evaluate an expression with a specific rounding mode. You do that by calling out to C, set the rounding mode, call back to Prolog and finally restore the rounding mode? Is that the picture?

Thanks for the link to <fenv.h>. As you have have seen in pl-arith.h, there is some stuff based on old APIs. Possibly requires some updating :slight_smile: What is not clear to me is what it means that these functions are considered thread-safe. Does that merely mean the last one wins, or is this environment thread-specific? That would make life a lot easier.

In any case, a better alternative to this alternative via C would be to provide fegetround() and fesetround() to Prolog, so you can so this and no longer need to deal with callbacks.

        feround(Old, rmode),
        feround(_, Old)).

Of course I also wonder why don’t simply set the mode as you want it and leave it there? Nothing in Prolog depends on rounding details AFAIK.

With the dust a little bit settled in C99, it might be a good idea to bring some of this to the Prolog level. ECLiPSe is way ahead with this stuff, so we should copy what we can if it is still valid in the current world.


C99 doesn’t specify, but from section 7.6 of C11:

The floating-point environment has thread storage duration. The initial state for a
thread’s floating-point environment is the current state of the floating-point environment
of the thread that creates it at the time of creation.

That’s a possibility although everything seen I’ve seen as examples recommends that rounding mode changes have a local (rather than global) effect. Usually rounding mode may not matter too much, but when it does this could lead to some very subtle bugs (as with any global data).

So a better scheme IMO is to create an is/3 predicate that could take an options parameter; one of the options could be rounding mode effective for the single evaluation. (Another could control treatment of non-numbers.) This would enforce a scope for the mode changes.

As above. I think this would dangerous for anything demanding strict adherence to IEEE 754 (defaults to round to nearest). Even for my use-case, the ability to switch between round upward and round downward is critical.

I fully support moving towards Eclipse compatibility on IEEE compliance. (I assume you mean .)

Even a simple PL_is function is a big win for me. But it does require a foreign library and all that entails for multi-platform support.

Thanks for even considering this; it’s a little off the beaten track.

Yes. Most of the hard work is actually already done as we already have ECLiPSe compatible Inf and Nan and -0.0. Only, Prolog cannot produce NaN or Inf as there is a little guard function that checks for these values and raises a Prolog exception.

For the rounding, I guess you refer to the float_rounding flag in this document and then make it writable?

I’m in favor of adopting the ECLiPSe proposal (after checking with Joachim it is still up-to-date). I don’t think it is a lot of work. There is quite a pile of things to do though, so please contact me offline if you want to discuss options for getting this done soon.

Something like that might be an option. In optimized mode (which you should use if you do a lot of arithmetic), the options could be compiled to a VM instruction and it would have little impact on performance.

I had another read of the proposal as it’s been a while. For rounding, the nexttoward function (I think there’s an equivalent predicate) functionally does what I need but I have a couple of minor concerns.

From the proposal:

This can be be used to perform safe rounding up or down, when following a primitive arithmetic operation that is known to be correct to within one ULP, thus supporting interval arithmetic.

(ULP=Unit in the last place) If the expression or more than a single operation, this condition may not hold unless the relevant IEEE rounding mode is in effect. If it is in effect a) I think this caveat can be dropped as the hardware should do this, and b) any subsequent rounding is unnecessary. The latter is a minor quibble and only affects the “tightness” of any calculated interval bounds.

I think almost any platform running Prolog will support the functions in <fenv.h>, but if not then I guess all rounding is done in software and this caveat still applies.

If the expression to be evaluated is just a float, then in needs to be explicitly rounded, i.e., no rounding mode will help. I assume that’s what nexttoward is already doing. A bit of care is needed evaluating pi and e to make sure an interval can be constructed which contains the real values of π and e. (i.e., nexttoward(pi, -1.0Inf) ≤ π ≤ nexttoward(pi, 1.0Inf))

My other minor concern/question is whether nexttoward does (or should) produce denormalized values (for very small values close to zero). My first thought was that it probably shouldn’t, but I don’t know whether it matters much.

Any idea of where this might sit on the priority list? (At least there shouldn’t be any syntax wars to wage.) The addition of a flag to control the guard function might be an easy way to get started. Followed by some sort of rounding control mechanism. (Just hoping.)