12.4 The Foreign Include File
AllApplicationManualNameSummaryHelp

  • Documentation
    • Reference manual
      • Foreign Language Interface
        • The Foreign Include File
          • Argument Passing and Control
            • PL_succeed()
            • PL_fail()
            • Non-deterministic Foreign Predicates
            • Yielding from foreign predicates
          • Atoms and functors
          • Analysing Terms via the Foreign Interface
          • Constructing Terms
          • Unifying data
          • Convenient functions to generate Prolog exceptions
          • Foreign language wrapper support functions
          • Serializing and deserializing Prolog terms
          • BLOBS: Using atoms to store arbitrary binary data
          • Exchanging GMP numbers
          • Calling Prolog from C
          • Discarding Data
          • String buffering
          • Foreign Code and Modules
          • Prolog exceptions in foreign code
          • Catching Signals (Software Interrupts)
          • Miscellaneous
          • Errors and warnings
          • Environment Control from Foreign Code
          • Querying Prolog
          • Registering Foreign Predicates
          • Foreign Code Hooks
          • Storing foreign data
          • Embedding SWI-Prolog in other applications
    • Packages

12.4.1 Argument Passing and Control

If Prolog encounters a foreign predicate at run time it will call a function specified in the predicate definition of the foreign predicate. The arguments 1, ... , <arity> pass the Prolog arguments to the goal as Prolog terms. Foreign functions should be declared of type foreign_t. Deterministic foreign functions have two alternatives to return control back to Prolog:

(return) foreign_t PL_succeed()
Succeed deterministically. PL_succeed is defined as return TRUE.
(return) foreign_t PL_fail()
Fail and start Prolog backtracking. PL_fail is defined as return FALSE.

12.4.1.1 Non-deterministic Foreign Predicates

By default foreign predicates are deterministic. Using the PL_FA_NONDETERMINISTIC attribute (see PL_register_foreign()) it is possible to register a predicate as a non-deterministic predicate. Writing non-deterministic foreign predicates is slightly more complicated as the foreign function needs context information for generating the next solution. Note that the same foreign function should be prepared to be simultaneously active in more than one goal. Suppose the natural_number_below_n/2 is a non-deterministic foreign predicate, backtracking over all natural numbers lower than the first argument. Now consider the following predicate:

quotient_below_n(Q, N) :-
        natural_number_below_n(N, N1),
        natural_number_below_n(N, N2),
        Q =:= N1 / N2, !.

In this predicate the function natural_number_below_n/2 simultaneously generates solutions for both its invocations.

Non-deterministic foreign functions should be prepared to handle three different calls from Prolog:

  • Initial call (PL_FIRST_CALL)
    Prolog has just created a frame for the foreign function and asks it to produce the first answer.
  • Redo call (PL_REDO)
    The previous invocation of the foreign function associated with the current goal indicated it was possible to backtrack. The foreign function should produce the next solution.
  • Terminate call (PL_PRUNED)
    The choice point left by the foreign function has been destroyed by a cut. The foreign function is given the opportunity to clean the environment.

Both the context information and the type of call is provided by an argument of type control_t appended to the argument list for deterministic foreign functions. The macro PL_foreign_control() extracts the type of call from the control argument. The foreign function can pass a context handle using the PL_retry*() macros and extract the handle from the extra argument using the PL_foreign_context*() macro.

(return) foreign_t PL_retry(intptr_t value)
The foreign function succeeds while leaving a choice point. On backtracking over this goal the foreign function will be called again, but the control argument now indicates it is a‘Redo' call and the macro PL_foreign_context() returns the handle passed via PL_retry(). This handle is a signed value two bits smaller than a pointer, i.e., 30 or 62 bits (two bits are used for status indication). Defined as return _PL_retry(n). See also PL_succeed().
(return) foreign_t PL_retry_address(void *)
As PL_retry(), but ensures an address as returned by malloc() is correctly recovered by PL_foreign_context_address(). Defined as return _PL_retry_address(n). See also PL_succeed().
int PL_foreign_control(control_t)
Extracts the type of call from the control argument. The return values are described above. Note that the function should be prepared to handle the PL_PRUNED case and should be aware that the other arguments are not valid in this case.
intptr_t PL_foreign_context(control_t)
Extracts the context from the context argument. If the call type is PL_FIRST_CALL the context value is 0L. Otherwise it is the value returned by the last PL_retry() associated with this goal (both if the call type is PL_REDO or PL_PRUNED).
void * PL_foreign_context_address(control_t)
Extracts an address as passed in by PL_retry_address().
predicate_t PL_foreign_context_predicate(control_t)

Fetch the Prolog predicate that is executing this function. Note that if the predicate is imported, the returned predicate refers to the final definition rather than the imported predicate; i.e., the module reported by PL_predicate_info() is the module in which the predicate is defined rather than the module where it was called. See also PL_predicate_info().

Note: If a non-deterministic foreign function returns using PL_succeed() or PL_fail(), Prolog assumes the foreign function has cleaned its environment. No call with control argument PL_PRUNED will follow.

The code of figure 5 shows a skeleton for a non-deterministic foreign predicate definition.

typedef struct                  /* define a context structure */
{ ...
} context;

foreign_t
my_function(term_t a0, term_t a1, control_t handle)
{ struct context * ctxt;

  switch( PL_foreign_control(handle) )
  { case PL_FIRST_CALL:
        if ( !(ctxt = malloc(sizeof *ctxt)) )
          return PL_resource_error("memory");
        <initialize ctxt>
        break;
    case PL_REDO:
        ctxt = PL_foreign_context_address(handle);
        break;
    case PL_PRUNED:
        ctxt = PL_foreign_context_address(handle);
        ...
        free(ctxt);
        return TRUE;
  }

  <find first/next solution from ctxt>
  ...
  // We fail */
  if ( <no_solution> )
  { free(ctx);
    return FALSE;
  }
  // We succeed without a choice point */
  if ( <last_solution> )
  { free(ctx);
    return TRUE;
  }
  // We succeed with a choice point */
  PL_retry_address(ctxt);
}
Figure 5 : Skeleton for non-deterministic foreign functions

12.4.1.2 Yielding from foreign predicates

Starting with SWI-Prolog 8.5.5 we provide an experimental interface that allows using a SWI-Prolog engine for asynchronous processing. The idea is that an engine that calls a foreign predicate which would need to block may be suspended and later resumed. For example, consider an application that listens to a large number of network connections (sockets). SWI-Prolog offers three scenarios to deal with this:

  1. Using a thread per connection. This model fits Prolog well as it allows to keep state in e.g. a DCG using phrase_from_stream/2. Maintaining an operating system thread per connection uses a significant amount of resources though.

  2. Using wait_for_input/3 a single thread can wait for many connections. Each time input arrives we must associate this with a state engine and advance this engine using a chunk of input of unknown size. Programming a state engine in Prolog is typically a tedious job. Although we can use delimited continuations (see section 4.9) in some scenarios this is not a universal solution.

  3. Using the primitives from this section we can create an engine (see PL_engine_create()) to handle a connection with the same benefits as using threads. When the engine calls a foreign predicate that would need to block it calls PL_yield_address() to suspend the engine. An overall scheduler watches for ready connections and calls PL_next_solution() to resume the suspended engine. This approach allows processing many connections on the same operating system thread.

As is, these features can only used through the foreign language interface. It was added after discussion with with Mattijs van Otterdijk aiming for using SWI-Prolog together with Rust's asynchronous programming support. Note that this feature is related to the engine API as described in section 11. It uis different though. Where the Prolog engine API allows for communicating with a Prolog engine, the facilities of this section merely allow an engine to suspend, to be resumed later.

To prepare a query for asynchronous usage we first create an engine using PL_create_engine(). Next, we create a query in the engine using PL_open_query() with the flags PL_Q_ALLOW_YIELD and PL_Q_EXT_STATUS. A foreign predicate that needs to be capable of suspending must be registered using PL_register_foreign() and the flags PL_FA_VARARGS and PL_FA_NONDETERMINISTIC; i.e., only non-det predicates can yield. This is no restriction as non-det predicate can always return TRUE to indicate deterministic success. Finally, PL_yield_address() allows the predicate to yield control, preparing to resume similar to PL_retry_address() does for non-deterministic results. PL_next_solution() returns PL_S_YIELD if a predicate calls PL_yield_address() and may be resumed by calling PL_next_solution() using the same query id (qid). We illustrate the above using some example fragments.

First, let us create a predicate that can read the available input from a Prolog stream and yield if it would block. Note that our predicate must the PL_FA_VARARGS interface, which implies the first argument is in a0, the second in a0+1, etc.212the other foreign interfaces do not support the yield API.

/** read_or_block(+Stream, -String) is det.
*/

#define BUFSIZE 4096

static foreign_t
read_or_block(term_t a0, int arity, void *context)
{ IOSTREAM *s;

  switch(PL_foreign_control(context))
  { case PL_FIRST_CALL:
      if ( PL_get_stream(a0, &s, SIO_INPUT) )
      { Sset_timeout(s, 0);
        break;
      }
      return FALSE;
    case PL_RESUME:
      s = PL_foreign_context_address(context);
      break;
    case PL_PRUNED:
      PL_release_stream(s);
      return TRUE;
    default:
      assert(0);
      return FALSE;
  }

  char buf[BUFSIZE];

  size_t n = Sfread(buf, sizeof buf[0], sizeof buf / sizeof buf[0], s);
  if ( n == 0 )                     // timeout or error
  { if ( (s->flags&SIO_TIMEOUT) )
      PL_yield_address(s);        // timeout: yield
    else
      return PL_release_stream(s);  // raise error
  } else
  { PL_release_stream(s);
    return PL_unify_chars(a0+1, PL_STRING|REP_ISO_LATIN_1, n, buf);
  }
}

This function must be registered using PL_register_foreign():

  PL_register_foreign("read_or_block", 2, read_or_block,
                      PL_FA_VARARGS|PL_FA_NONDETERMINISTIC);

Next, create an engine to run handle_connection/1 on a Prolog stream. Note that we omitted most of the error checking for readability. Also note that we must make our engine current using PL_set_engine() before we can interact with it.

qid_t
start_connection(IOSTREAM *c)
{ predicate_t p = PL_predicate("handle_connection", 1, "user");

  PL_engine_t e = PL_create_engine(NULL);
  PL_engine_t old;
  if ( PL_set_engine(e, &old) )
  { term_t av = PL_new_term_refs(1);
    PL_unify_stream(av+0, c);
    qid_t q = PL_open_query(e, NULL,
                            PL_Q_NORMAL|PL_Q_ALLOW_YIELD|PL_Q_EXT_STATUS,
                            p, av);
    PL_set_engine(old, NULL);
    return q;
  } /* else error */
}

Finally, our foreign code must manage this engine. Normally it will do so together with many other engines. First, we write a function that runs a query in the engine to which it belongs.213Possibly, future versions of PL_next_solution() may do that although the value is in general limited because interacting with the arguments of the query requires the query's engine to be current anyway.

int
PL_engine_next_solution(qid_t qid)
{ PL_engine_t old;
  int rc;

  if ( PL_set_engine(PL_query_engine(qid), &old) == PL_ENGINE_SET )
  { rc = PL_next_solution(qid);

    PL_set_engine(old, NULL);
  } else
    rc = FALSE;

  return rc;
}

Now we can simply handle a connection using the loop below which restarts the query as long as it yields. Realistic code manages multiple queries and will (in this case) use the POSIX poll() or select() interfaces to activate the next query that can continue without blocking.

  int rc;
  do
  { rc = PL_engine_next_solution(qid);
  } while( rc == PL_S_YIELD );

After the query completes it must be closed using PL_close_query() or PL_cut_query(). The engine may be destroyed using PL_engine_destroy() or reused for a new query.

(return) foreign_t PL_yield_address(void *)
Cause PL_next_solution() of the active query to return with PL_S_YIELD. A subsequent call to PL_next_solution() on the same query calls the foreign predicate again with the control status set to PL_RESUME, after which PL_foreign_context_address() retrieves the address passed to this function. The state of the Prolog engine is maintained, including term_t handles. If the passed address needs to be invalidated the predicate must do so when returning either TRUE or FALSE. If the engine terminates the predicate the predicate is called with status PL_PRUNED, in which case the predicate must cleanup.
int PL_can_yield(void)
Returns TRUE when called from inside a foreign predicate if the query that (indirectly) calls this foreign predicate can yield using PL_yield_address(). Returns FALSE when either there is no current query or the query cannot yield.

Discussion

Asynchronous processing has become popular with modern programming languages, especially those aiming at network communication. Asynchronous processing uses fewer resources than threads while avoiding most of the complications associated with thread synchronization if only a single thread is used to manage the various states. The lack of good support for destructive state updates in Prolog makes it attractive to use threads for dealing with multiple inputs. The fact that Prolog discourages using shared global data such as dynamic predicates typically makes multithreaded code easy to manage.

It is not clear how much scalability we gain using Prolog engines instead of Prolog threads. The only difference between the two is the operating system task. Prolog engines are still rather memory intensive, mainly depending on the stack sizes. Global garbage collection (atoms and clauses) need to process all the stacks of all the engines and thus limit scalability.

One possible future direction is to allow all (possibly) blocking Prolog predicates to use the yield facility and provide a Prolog API to manage sets of engines that use this type of yielding. As is, these features are designed to allow SWI-Prolog for cooperating with languages that provide asynchronous functions.