Next: , Previous: Pragmas, Up: Top   [Contents]


21 Implementation-dependent extensions

The Melbourne Mercury implementation supports the following extensions to the Mercury language:


21.1 Fact tables

Large tables of facts can be compiled using a different algorithm that is more efficient and can produce more efficient code.

Declarations of the forms

:- pragma fact_table(pred(Name/Arity), FileName).
:- pragma fact_table(func(Name/Arity), FileName).

tell the compiler that the predicate or function with name Name and arity Arity is defined by a set of facts in an external file FileName. Defining large tables of facts in this way allows the compiler to use a more efficient algorithm for compiling them. This algorithm uses less memory than would normally be required to compile the facts, so much larger tables are possible.

Each mode is indexed on all its input arguments so the compiler can produce very efficient code using this technique.

In the current implementation, the table of facts is compiled into a separate C file named FileName.c. The compiler will automatically generate the correct dependencies for this file when the command ‘mmake main_module.depend’ is invoked. This ensures that the C file will be compiled to FileName.o and then linked with the other object files when ‘mmake main_module’ is invoked.

The main limitation of the ‘fact_table’ pragma is that in the current implementation, predicates or functions defined as fact tables can only have arguments of types string, int or float.

Another limitation is that the ‘--high-level-code’ back-end does not support ‘pragma fact_table’ for procedures with determinism nondet or multi.


21.2 Tabled evaluation

(Note: “Tabled evaluation” has no relation to the “fact tables” described above.)

Ordinarily, the results of each procedure call are not recorded; if the same procedure is called with the same arguments, then the answer(s) must be recomputed again. For some procedures, this recomputation can be very wasteful.

With tabled evaluation, the implementation keeps a table containing the previously computed results of the specified procedure; this table is sometimes called the memo table (since it “remembers” previous answers). At each procedure call, the implementation will search the memo table to check whether the answer(s) have already been computed, and if so, the answers will be returned directly from the memo table rather than being recomputed. This can result in much faster execution, at the cost of additional space to record answers in the table.

The implementation can also check at runtime for the situation where a procedure calls itself recursively with the same arguments, which would normally result in an infinite loop; if this situation is encountered, it can (at the programmer’s option) either throw an exception, or avoid the infinite loop by computing solutions using a “minimal model” semantics. (Specifically, the minimal model computed by our implementation is the perfect model.)

When targeting the generation of C code, the current Mercury implementation supports three different pragmas for tabling, to cover these three cases: ‘loop_check’, ‘memo’, and ‘minimal_model’. (None of these are supported when targeting the generation of C# or Java code.)

The syntax for each of these declarations is

:- pragma memo(pred(Name/Arity)).
:- pragma memo(pred(Name/Arity), [list of tabling attributes]).
:- pragma loop_check(pred(Name/Arity)).
:- pragma loop_check(pred(Name/Arity), [list of tabling attributes]).
:- pragma minimal_model(pred(Name/Arity)).
:- pragma minimal_model(pred(Name/Arity), [list of tabling attributes]).

and the corresponding versions in which ‘pred’ is replaced with ‘func’. The ‘pred(Name/Arity)’ or ‘func(Name/Arity)’ part specifies the predicate or function to which the declaration applies. At most one of these declarations may be specified for any given predicate or function.

Pragmas using the above syntax specify a declaration that applies to all the modes of a predicate or function. Programmers can request the application of tabling to only one particular mode of a predicate or function, via declarations such as these:

:- pragma memo(Name(in, in, out)).
:- pragma memo(Name(in, in, out), [list of tabling attributes]).
:- pragma loop_check(Name(in, out)).
:- pragma loop_check(Name(in, out), [list of tabling attributes]).
:- pragma minimal_model(Name(in, in, out, out)).
:- pragma minimal_model(Name(in, in, out, out), [list of tabling attributes]).

All the above example pragmas are for predicates. For functions, the first argument of the pragma would include the mode of the function result as well, like this:

:- pragma memo(Name(in, in) = out, [list of tabling attributes]).

Because all variants of tabling record the values of input arguments, and all except ‘loop_check’ also record the values of output arguments, you cannot apply any of these pragmas to procedures whose arguments’ modes include any unique component.

Tabled evaluation of a predicate or function that has an argument whose type is a foreign type will result in a run-time error, unless the foreign type is one for which the ‘can_pass_as_mercury_type’ and ‘stable’ assertions have been made (see Using foreign types from Mercury).

The optional list of attributes allows programmers to control some aspects of the management of the memo table(s) of the procedure(s) affected by the pragma.

The ‘allow_reset’ attribute asks the compiler to define a predicate that, when called, resets the memo table. The name of this predicate will be “table_reset_for”, followed by the name of the tabled predicate, followed by its arity, and (if the predicate has more than one mode) by the mode number (the first declared mode is mode 0, the second is mode 1, and so on). These three or four components are separated by underscores. The reset predicate takes a di/uo pair of I/O states as arguments. The presence of these I/O state arguments in the reset predicate, and the fact that tabled predicates cannot have unique arguments together imply that a memo table cannot be reset while a call using that memo table is active.

The ‘statistics’ attribute asks the compiler to define a predicate that, when called, returns statistics about the memo table. The name of this predicate will be “table_statistics_for”, followed by the name of the tabled predicate, followed by its arity, and (if the predicate has more than one mode) by the mode number (the first declared mode is mode 0, the second is mode 1, and so on). These three or four components are separated by underscores. The statistics predicate takes three arguments. The second and third are a di/uo pair of I/O states, while the first is an output argument that contains information about accesses to and modifications of the procedure’s memo table, both since the creation of the table, and since the last call to this predicate. The type of this argument is defined in the file table_builtin.m in the Mercury standard library. That module also contains a predicate for printing out this information in a programmer-friendly format.

As mentioned above, the Mercury compiler implements tabling only when targeting the generation of C code. In other grades, the compiler normally generates a warning for each tabling pragma that it is forced to ignore. The ‘disable_warning_if_ignored’ attribute tells the compiler not to generate such a warning for the pragma it is attached to. Since the ‘loopcheck’ and ‘minimal_model’ pragmas affect the semantics of the program, and such changes should not be made silently, this attribute may not be specified for them. But this attribute may be specified for ‘memo’ pragmas, since these affect only the program’s performance, not its semantics.

The remaining two attributes, ‘fast_loose’ and ‘specified’, control how arguments are looked up in the memo table. The default implementation looks up the value of each input argument, and thus requires time proportional to the number of function symbols in the input arguments. This is the only implementation allowed for minimal model tabling, but for predicates tabled with the ‘loop_check’ and ‘memo’ pragmas, programmers can also choose some other tabling methods.

The ‘fast_loose’ attribute asks the compiler to generate code that looks up only the address of each input argument in the memo table, which means that the time required is linear only in the number of input arguments, not their size. The tradeoff is that ‘fast_loose’ does not recognize calls as duplicates if they involve input arguments that are logically equal but are stored at different locations in memory. The following declaration calls for this variant of tabling.

:- pragma memo(Name(in, in, in, out),
        [allow_reset, statistics, fast_loose]).

The ‘specified’ attribute allows programmers to choose individually, for each input argument, whether that argument should be looked up in the memo table by value or by address, or whether it should be looked up at all:

:- pragma memo(Name(in, in, in, out), [allow_reset, statistics,
        specified([value, addr, promise_implied, output])]).

The ‘specified’ attribute should have an argument which is a list, and this list should contain one element for each argument of the predicate or function concerned (if a function, the last element is for the return value). For output arguments, the list element should be ‘output’. For input arguments, the list element may be ‘value’, ‘addr’ or ‘promise_implied’. The first calls for tabling the argument by value, the second calls for tabling the argument by address, and the third calls for not tabling the argument at all. This last course of action promises that any two calls that agree on the values of the value-tabled input arguments and on the addresses of the address-tabled input arguments will behave the same regardless of the values of the untabled input arguments. In most cases, this will mean that the values of the untabled arguments are implied by the values of the value-tabled arguments and the addresses of the address-tabled arguments, though the promise can also be fulfilled if the table predicate or function does not actually use the untabled argument for computing any of its output. (It is ok for it to use the untabled argument to decide what exception to throw.)

If the tabled predicate or function has only one mode, then a declaration like this can also be specified without giving the argument modes:

:- pragma memo(pred(Name/Arity), [allow_reset, statistics,
        specified([value, addr, promise_implied, output])]).

Note that a ‘pragma minimal_model’ declaration changes the declarative semantics of the specified predicate or function: instead of using the completion of the clauses as the basis for the semantics, as is normally the case in Mercury, the declarative semantics that is used is a “minimal model” semantics, specifically, the perfect model semantics. Anything which is true or false in the completion semantics is also true or false (respectively) in the perfect model semantics, but there are goals for which the perfect model specifies that the result is true or false, whereas the completion semantics leaves the result unspecified. For these goals, the usual Mercury semantics requires the implementation to either loop or report an error message, but the perfect model semantics requires a particular answer to be returned. In particular, the perfect model semantics says that any call that is not true in all models is false.

Programmers should therefore use a ‘pragma minimal_model’ declaration only in cases where their intended interpretation for a procedure coincides with the perfect model for that procedure. Fortunately, however, this is usually what programmers intend.

For more information on tabling, see K. Sagonas’s PhD thesis The SLG-WAM: A Search-Efficient Engine for Well-Founded Evaluation of Normal Logic Programs. See [4]. The operational semantics of procedures with a ‘pragma minimal_model’ declaration corresponds to what Sagonas calls “SLGd resolution”.

In the general case, the execution mechanism required by minimal model tabling is quite complicated, requiring the ability to delay goals and then wake them up again. The Mercury implementation uses a technique based on copying relevant parts of the stack to the heap when delaying goals. It is described in Tabling in Mercury: design and implementation by Z. Somogyi and K. Sagonas, Proceedings of the Eight International Symposium on Practical Aspects of Declarative Languages.

Please note: the current implementation of tabling does not support all the possible compilation grades (see the “Compilation model options” section of the Mercury User’s Guide) allowed by the Mercury implementation. In particular, minimal model tabling is incompatible with high level code and the use of trailing.


21.3 Termination analysis

The compiler includes a termination analyser which can be used to prove termination of predicates and functions. Details of the analysis is available in “Termination Analysis for Mercury” by Chris Speirs, Zoltan Somogyi and Harald Sondergaard. See [1].

The analysis is based on an algorithm proposed by Gerhard Groger and Lutz Plumer in their paper “Handling of mutual recursion in automatic termination proofs for logic programs.” See [2].

For an introduction to termination analysis for logic programs, please refer to “Termination Analysis for Logic Programs” by Chris Speirs. See [3].

Information about the termination properties of a predicate or function can be given to the compiler. Pragmas are also available to require the compiler to prove termination of a given predicate or function, or to give an error message if it cannot do so.

The analyser is enabled by the option ‘--enable-termination’, which can be abbreviated to ‘--enable-term’. When termination analysis is enabled, any predicates or functions with a ‘check_termination’ pragma defined on them will have their termination checked, and if termination cannot be proved, the compiler will emit an error message detailing the reason that termination could not be proved.

The option ‘--check-termination’, which may be abbreviated to ‘--check-term’ or ‘--chk-term’, forces the compiler to check the termination of all predicates in the module. It is common for the compiler to be unable to prove termination of some predicates and functions because they call other predicates which could not be proved to terminate or because they use language features (such as higher-order calls) which cannot be usefully analysed. In this case, the compiler only emits a warning for these predicates and functions if the ‘--verbose-check-termination’ option is enabled. For every predicate or function that the compiler cannot prove the termination of, a warning message is emitted, but compilation continues. The ‘--check-termination’ option implies the ‘--enable-termination’ option.

The accuracy of the termination analysis is substantially degraded if intermodule optimization is not enabled. Unless intermodule optimization is enabled, the compiler must assume that any imported predicate may not terminate.

By default, the compiler assumes that a procedure defined using the foreign language interface will terminate for all input if it does not call Mercury. If it does call Mercury then by default the compiler will assume that it may not terminate.

The foreign code attributes ‘terminates’/‘does_not_terminate’ may be used to force the compiler to treat a foreign_proc as terminating/non-terminating irrespective of whether it calls Mercury. As a matter of style, it is preferable to use foreign code attributes for foreign_procs rather than the termination pragmas described below.

The following declarations can be used to inform the compiler of the termination properties of a predicate or function.

:- pragma terminates(pred(Name/Arity)).
:- pragma terminates(func(Name/Arity)).

This declaration may be used to inform the compiler that this predicate or function is guaranteed to terminate for any input. This is useful when the compiler cannot prove termination of some predicates or functions which are in turn preventing the compiler from proving termination of other predicates or functions. This declaration affects not only the predicate specified but also any other predicates that are mutually recursive with it.

:- pragma does_not_terminate(pred(Name/Arity)).
:- pragma does_not_terminate(func(Name/Arity)).

This declaration may be used to inform the compiler that this predicate or function may not terminate. This declaration affects not only the predicate or function specified but also any other predicates and/or functions that are mutually recursive with it.

:- pragma check_termination(pred(Name/Arity)).
:- pragma check_termination(func(Name/Arity)).

This pragma tells the compiler that it should try to prove the termination of this predicate or function, and if it fails, then it should quit with an error message.


21.4 Feature sets

The Melbourne Mercury implementation supports a number of optional compilation model features, such as Trailing or Tabled evaluation. Feature sets allow the programmer to assert that a module requires the presence of one or more optional features in the compilation model. These assertions can be made using a ‘pragma require_feature_set’ declaration.

The ‘require_feature_set’ pragma declaration has the following form:

:- pragma require_feature_set(Features).

where Features is a (possibly empty) list of features.

The supported features are:

concurrency

This specifies that the compilation model must support concurrent execution of multiple threads.

single_prec_float

This specifies that the compilation model must use single precision floats. This feature cannot be specified together with the ‘double_prec_float’ feature.

double_prec_float’,

This feature specifies tha the compilation model must use double precision floats. This feature cannot be specified together with the ‘single_prec_float’ feature.

memo

This feature specifies that the compilation model must support memoisation (see Tabled evaluation).

parallel_conj

This feature specifies that the compilation model must support parallel execution of conjunctions. This feature cannot be specified together with the ‘trailing’ feature.

trailing

This feature specifies that the compilation model must support trailing, see Trailing. This feature cannot be specified together with the ‘parallel_conj’ feature.

strict_sequential

This feature specifies that a semantics that is equivalent to the strict sequential operational semantics must be used.

conservative_gc

This feature specifies that a module requires conservative garbage collection. This feature is only checked when using the C backends; it is ignored by the non-C backends.

When a module containing a ‘pragma require_feature_set’ declaration is compiled, the implementation checks to see that the specified features are supported by the compilation model. It emits an error if they are not.

A ‘pragma require_feature_set’ may only occur in the implementation section of a module.

A ‘pragma require_feature_set’ affects only the module in which it occurs; in particular it does not affect any submodules.

If a module contains multiple ‘pragma require_feature_set’ declarations, then the implementation should emit an error if any of them specifies a feature that is not supported by the compilation model.


21.5 Trailing

In certain compilation grades (see the “Compilation model options” section of the Mercury User’s Guide), the Melbourne Mercury implementation supports trailing. Trailing is a means of having side-effects, such as destructive updates to data structures, undone on backtracking. The basic idea is that during forward execution, whenever you perform a destructive modification to a data structure that may still be live on backtracking, you should record whatever information is necessary to restore it on a stack-like data structure called the “trail”. Then, if a computation fails, and execution backtracks to before those updates were performed, the Mercury runtime engine will traverse the trail back to the most recent choice point, undoing all those updates.

The interface used is a set of C functions (which are actually implemented as macros) and types. Typically these will be called from C code within ‘pragma foreign_proc’ or ‘pragma foreign_code’ declarations in Mercury code.

For an example of the use of this interface, see the module extras/trailed_update/tr_array.m in the Mercury extras distribution.


21.5.1 Choice points

A “choice point” is a point in the computation to which execution might backtrack when a goal fails or throws an exception. The “current” choice point is the one that was most recently encountered; that is also the one to which execution will branch if the current computation fails.

When you trail an update, the Mercury engine will ensure that if execution ever backtracks to the choice point that was current at the time of trailing, then the update will be undone.

If the Mercury compiler determines that it will never need to backtrack to a particular choice point, then it will “prune” away that choice point. If a choice point is pruned, the trail entries for those updates will not necessarily be discarded, because in general they may still be necessary in case we backtrack to a prior choice point.


21.5.2 Value trailing

The simplest form of trailing is value trailing. This allows you to trail updates to memory and have the Mercury runtime engine automatically undo them on backtracking.

MR_trail_value()

Prototype:

void MR_trail_value(MR_Word *address, MR_Word value);

Ensures that if future execution backtracks to the current choice point, then value will be placed in address.


MR_trail_current_value()

Prototype:

void MR_trail_current_value(MR_Word *address);

Ensures that if future execution backtracks to the current choice point, the value currently in address will be restored.

MR_trail_current_value(address)’ is equivalent to ‘MR_trail_value(address, *address)’.

Note that address must be word aligned for both MR_trail_current_value() and MR_trail_value(). (The address of Mercury data structures that have been passed to C via the foreign language interface are guaranteed to be appropriately aligned.)


21.5.3 Function trailing

For more complicated uses of trailing, you can store the address of a C function on the trail and have the Mercury runtime call your function back whenever future execution backtracks to the current choice point or earlier, or whenever that choice point is pruned, because execution commits to never backtracking over that point, or whenever that choice point is garbage collected.

Note the garbage collector in the current Mercury implementation does not garbage-collect the trail; this case is mentioned only so that we can cater for possible future extensions.

MR_trail_function()

Prototype:

typedef enum {
        MR_undo,
        MR_exception,
        MR_retry,
        MR_commit,
        MR_solve,
        MR_gc
} MR_untrail_reason;

void MR_trail_function(
        void (*untrail_func)(void *, MR_untrail_reason),
        void *value
);

A call to ‘MR_trail_function(untrail_func, value)’ adds an entry to the function trail. The Mercury implementation ensures that if future execution ever backtracks to the current choicepoint, or backtracks past the current choicepoint to some earlier choicepoint, then (*untrail_func)(value, reason) will be called, where reason will be ‘MR_undo’ if the backtracking was due to a goal failing, ‘MR_exception’ if the backtracking was due to a goal throwing an exception, or ‘MR_retry’ if the backtracking was due to the use of the “retry” command in ‘mdb’, the Mercury debugger, or any similar user request in a debugger. The Mercury implementation also ensures that if the current choice point is pruned because execution commits to never backtracking to it, then (*untrail_func)(value, MR_commit) will be called. It also ensures that if execution requires that the current goal be solvable, then (*untrail_func)(value, MR_solve) will be called. This happens in calls to solutions/2, for example. (MR_commit is used for “hard” commits, i.e. when we commit to a solution and prune away the alternative solutions; MR_solve is used for “soft” commits, i.e. when we must commit to a solution but do not prune away all the alternatives.)

MR_gc is currently not used — it is reserved for future use.

Typically if the untrail_func is called with reason being ‘MR_undo’, ‘MR_exception’, or ‘MR_retry’, then it should undo the effects of the update(s) specified by value, and then free any resources associated with that trail entry. If it is called with reason being ‘MR_commit’ or ‘MR_solve’, then it should not undo the update(s); instead, it may check for floundering (see the next section). In the ‘MR_commit’ case it may, in some cases, be possible to also free resources associated with the trail entry. If it is called with anything else (such as ‘MR_gc’), then it should probably abort execution with an error message.

Note that the address of the C function passed as the first argument of MR_trail_function() must be word aligned.


21.5.4 Delayed goals and floundering

Another use for the function trail is check for floundering in the presence of delayed goals.

Often, when implementing certain kinds of constraint solvers, it may not be possible to actually solve all of the constraints at the time they are added. Instead, it may be necessary to simply delay their execution until a later time, in the hope the constraints may become solvable when more information is available. If you do implement a constraint solver with these properties, then at certain points in the computation — for example, after executing a negated goal — it is important for the system to check that there are no outstanding delayed goals which might cause failure, before execution commits to this execution path. If there are any such delayed goals, the computation is said to “flounder”. If the check for floundering was omitted, then it could lead to unsound behaviour, such as a negation failing even though logically speaking it ought to have succeeded.

The check for floundering can be implemented using the function trail, by simply calling ‘MR_trail_function()’ to add a function trail entry whenever you create a delayed goal, and putting the appropriate check for floundering in the ‘MR_commit’ and ‘MR_solve’ cases of your function. The Mercury extras distribution includes an example of this: see the ‘ML_var_untrail_func()’ function in the file extras/trailed_update/var.m.) If your function does detect floundering, then it should print an error message and then abort execution.


21.5.5 Avoiding redundant trailing

If a mutable data structure is updated multiple times, and each update is recorded on the trail using the functions described above, then some of this trailing may be redundant. It is generally not necessary to record enough information to recover the original state of the data structure for every update on the trail; instead, it is enough to record the original state of each updated data structure just once for each choice point occurring after the data structure is allocated, rather than once for each update.

The functions described below provide a means to avoid redundant trailing.

MR_ChoicepointId

Declaration:

typedef … MR_ChoicepointId;

The type MR_ChoicepointId is an abstract type used to hold the identity of a choice point. Values of this type can be compared using C’s ‘==’ operator or using ‘MR_choicepoint_newer()’.


MR_current_choicepoint_id()

Prototype:

MR_ChoicepointId MR_current_choicepoint_id(void);

MR_current_choicepoint_id() returns a value indicating the identity of the most recent choice point; that is, the point to which execution would backtrack if the current computation failed. The value remains meaningful if the choicepoint is pruned away by a commit, but is not meaningful after backtracking past the point where the choicepoint was created (since choicepoint ids may be reused after backtracking).


MR_null_choicepoint_id()

Prototype:

MR_ChoicepointId MR_null_choicepoint_id(void);

MR_null_choicepoint_id() returns a “null” value that is distinct from any value ever returned by MR_current_choicepoint_id. (Note that MR_null_choicepoint_id() is a macro that is guaranteed to be suitable for use as a static initializer, so that it can for example be used to provide the initial value of a C global variable.)


MR_choicepoint_newer()

Prototype:

bool MR_choicepoint_newer(MR_ChoicepointId, MR_ChoicepointId);

MR_choicepoint_newer(x, y) true iff the choicepoint indicated by x is newer than (i.e. was created more recently than) the choicepoint indicated by y. The null ChoicepointId is considered older than any non-null ChoicepointId. If either of the choice points have been backtracked over, the behaviour is undefined.

The way these functions are generally used is as follows. When you create a mutable data structure, you should call MR_current_choicepoint_id() and save the value it returns as a ‘prev_choicepoint’ field in your data structure. When you are about to modify your mutable data structure, you can then call MR_current_choicepoint_id() again and compare the result from that call with the value saved in the ‘prev_choicepoint’ field in the data structure using MR_choicepoint_newer(). If the current choicepoint is newer, then you must trail the update, and update the ‘prev_choicepoint’ field with the new value; furthermore, you must also take care that on backtracking the previous value of the ‘prev_choicepoint’ field in your data structure is restored to its previous value, by trailing that update too. But if MR_current_choice_id() is not newer than the prev_choicepoint field, then you can safely perform the update to your data structure without trailing it.

If your mutable data structure is a C global variable, then you can use MR_null_choicepoint_id() for the initial value of the ‘prev_choicepoint’ field. If on the other hand your mutable data structure is created by a predicate or function that uses tabled evaluation (see Tabled evaluation), then you should use MR_null_choicepoint_id() for the initial value of the field. Doing so will ensure that the data will be reset to its initial value if execution backtracks to a point before the mutable data structure was created, which is important because this copy of the mutable data structure will be tabled and will therefore be produced again if later execution attempts to create another instance of it.

For an example of avoiding redundant trailing, see the sample module below.

Note that there is a cost to this — you have to include an extra field in your data structure for each part of the data structure which you might update, you need to perform a test for each update to decide whether or not to trail it, and if you do need to trail the update, then you have an extra field that you need to trail. Whether or not the benefits from avoiding redundant trailing outweigh these costs will depend on your application.

:- module trailing_example.
:- interface.

:- type int_ref.

    % Create a new int_ref with the specified value.
    %
:- pred new_int_ref(int_ref::uo, int::in) is det.

    % update_int_ref(Ref0, Ref, OldVal, NewVal).
    % Ref0 has value OldVal and Ref has value NewVal.
    %
:- pred update_int_ref(int_ref::mdi, int_ref::muo, int::out, int::in)
    is det.

:- implementation.

:- pragma foreign_decl("C", "

typedef struct {
    MR_ChoicepointId prev_choicepoint;
    MR_Integer data;
} C_IntRef;

").

:- pragma foreign_type("C", int_ref, "C_IntRef *").

:- pragma foreign_proc("C",
    new_int_ref(Ref::uo, Value::in),
    [will_not_call_mercury, promise_pure],
"
    C_Intref *x = malloc(sizeof(C_IntRef));
    x->prev_choicepoint = MR_current_choicepoint_id();
    x->data = Value;
    Ref = x;
").

:- pragma foreign_proc("C",
    update_int_ref(Ref0::mdi, Ref::muo, OldValue::out, NewValue::in),
    [will_not_call_mercury, promise_pure],
"
    C_IntRef *x = Ref0;
    OldValue = x->data;

    /* Check whether we need to trail this update. */
    if (MR_choicepoint_newer(MR_current_choicepoint_id(),
        x->prev_choicepoint))
    {
        /*
        ** Trail both x->data and x->prev_choicepoint,
        ** since we're about to update them both.
        */
        assert(sizeof(x->data) == sizeof(MR_Word));
        assert(sizeof(x->prev_choicepoint) == sizeof(MR_Word));
        MR_trail_current_value((MR_Word *)&x->data);
        MR_trail_current_value((MR_Word *)&x->prev_choicepoint);

        /*
        ** Update x->prev_choicepoint to indicate that
        ** x->data's previous value has been trailed
        ** at this choice point.
        */
        x->prev_choicepoint = MR_current_choicepoint_id();
    }
    x->data = NewValue;
    Ref = Ref0;
").


Previous: Delayed goals and floundering, Up: Trailing   [Contents]