Skip to content

Commit 786ec52

Browse files
committed
ADDED: push_prolog_flag/2 and pop_prolog_flag/1
Thread-local, nestable save/restore of Prolog flag values. Both go through the existing set_prolog_flag/2 code path, so type checks, read-only checks, thread-local COW, and side effects (character escapes, module bits, boolean fast mask, ...) stay consistent. The push stack lives inside the prolog_flag struct as a linked list of snapshot frames. A FF_PUSH_ABSENT sentinel records that the flag did not exist at push time; pop then removes the local entry.
1 parent c5f2fd6 commit 786ec52

6 files changed

Lines changed: 312 additions & 10 deletions

File tree

man/overview.plx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2804,6 +2804,43 @@ both for the calling thread and in the global flag table. See the
28042804
current_prolog_flag/2, print a warning. This option is used
28052805
for options set using the commandline option \verb$-D<flag>[=<value>]$.
28062806
\end{description}
2807+
2808+
\predicate{push_prolog_flag}{2}{:Key, +Value}
2809+
Save the current thread-local value of \arg{Key} and set it to
2810+
\arg{Value}. If \arg{Key} does not exist, it is created (only in the
2811+
calling thread) and the pushed state records its absence. Nestable and
2812+
paired with pop_prolog_flag/1. These predicates have been designed
2813+
with two main use cases in mind. First of all scoped compilation
2814+
with different flags. For example, the code below keeps the clause
2815+
as written. Without locally modifying this flag the \exam{X = 42}
2816+
is normally moved into the head.
2817+
2818+
\begin{code}
2819+
:- push_prolog_flag(optimise_unify, false).
2820+
p(X) :-
2821+
X = 42,
2822+
format('The answer to the ultimate question~n').
2823+
:- pop_prolog_flag(optimise_unify).
2824+
\end{code}
2825+
2826+
Second, runtime scoping. For example, execute a goal with \jargon{occurs
2827+
checking}:\footnote{Be aware that the ``pop'' happens when \arg{Goal}
2828+
has completed. Notably it is not immediately executed if \arg{Goal}
2829+
succeeds with a choice point.}
2830+
2831+
\begin{code}
2832+
call_with_occurs_check(Goal) :-
2833+
setup_call_cleanup(
2834+
push_prolog_flag(occurs_check, true),
2835+
Goal,
2836+
pop_prolog_flag(occurs_check)).
2837+
\end{code}
2838+
2839+
\predicate{pop_prolog_flag}{1}{:Key}
2840+
Restore the state saved by the matching push_prolog_flag/2. If \arg{Key}
2841+
did not exist at the matching push, it is removed again. Raises
2842+
\except{existence_error(pushed_flag, Key)} if no matching push exists
2843+
on the current thread.
28072844
\end{description}
28082845
28092846

man/summary.plx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ suggest predicates from a keyword.
519519
\predicatesummary{phrase_from_quasi_quotation}{2}{Parse quasi quotation with DCG}
520520
\predicatesummary{please}{3}{Query/change environment parameters}
521521
\predicatesummary{plus}{3}{Logical integer addition}
522+
\predicatesummary{pop_prolog_flag}{1}{Restore a flag saved by push_prolog_flag/2}
522523
\predicatesummary{portray}{1}{\hook{user} Modify behaviour of print/1}
523524
\predicatesummary{predicate_property}{2}{Query predicate attributes}
524525
\predicatesummary{predsort}{3}{Sort, using a predicate to determine the order}
@@ -563,6 +564,7 @@ suggest predicates from a keyword.
563564
\predicatesummary{protocola}{1}{Append log of the user interaction to file}
564565
\predicatesummary{protocolling}{1}{On what file is user interaction logged}
565566
\predicatesummary{public}{1}{Declaration that a predicate may be called}
567+
\predicatesummary{push_prolog_flag}{2}{Save a flag and set a new value}
566568
\predicatesummary{put}{1}{Write a character}
567569
\predicatesummary{put}{2}{Write a character on a stream}
568570
\predicatesummary{put_assoc}{4}{Add Key-Value to association tree}

src/ATOMS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ A protocol "protocol"
703703
A prove ":-"
704704
A public "public"
705705
A punct "punct"
706+
A pushed_flag "pushed_flag"
706707
A qlf "qlf"
707708
A qlf_min_load "qlf_min_load"
708709
A quasi_quotation "quasi_quotation"

src/os/pl-prologflag.c

Lines changed: 210 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ setPrologFlag(const char *name, unsigned int flags, ...)
250250
f->index = 0;
251251
f->flags = flags;
252252
f->oneof = NULL;
253+
f->pushed = NULL;
253254
addNewHTableWP(GD->prolog_flag.table, an, f);
254255
first_def = true;
255256
}
@@ -326,22 +327,32 @@ clean_prolog_flag(prolog_flag *f)
326327
oneof *of;
327328

328329
set_flag_type(f, FT_INTEGER);
329-
switch(type)
330-
{ case FT_TERM:
331-
PL_erase(f->value.t);
332-
break;
333-
case FT_ATOM:
334-
PL_unregister_atom(f->value.a);
335-
break;
336-
default:
337-
;
330+
if ( !(f->flags & FF_PUSH_ABSENT) )
331+
{ switch(type)
332+
{ case FT_TERM:
333+
PL_erase(f->value.t);
334+
break;
335+
case FT_ATOM:
336+
PL_unregister_atom(f->value.a);
337+
break;
338+
default:
339+
;
340+
}
338341
}
339342
memset(&f->value, 0, sizeof(f->value));
340343

341344
if ( (of=f->oneof) && --of->references == 0 )
342345
{ f->oneof = NULL;
343346
free_oneof(of);
344347
}
348+
349+
while ( f->pushed )
350+
{ prolog_flag *p = f->pushed;
351+
f->pushed = p->pushed;
352+
p->pushed = NULL;
353+
clean_prolog_flag(p);
354+
freeHeap(p, sizeof(*p));
355+
}
345356
}
346357

347358
#ifdef O_PLMT
@@ -350,6 +361,7 @@ copy_prolog_flag(const prolog_flag *f)
350361
{ prolog_flag *copy = allocHeapOrHalt(sizeof(*copy));
351362

352363
*copy = *f;
364+
copy->pushed = NULL;
353365
if ( f->oneof )
354366
f->oneof->references++;
355367
switch((f->flags & FT_MASK))
@@ -1087,6 +1099,7 @@ new_prolog_flag(DECL_LD term_t value, unsigned int flags)
10871099
prolog_flag *f = allocHeapOrHalt(sizeof(*f));
10881100
f->index = 0;
10891101
f->oneof = NULL;
1102+
f->pushed = NULL;
10901103

10911104
switch( (flags & FT_MASK) )
10921105
{ case FT_FROM_VALUE:
@@ -1633,6 +1646,192 @@ PRED_IMPL("create_prolog_flag", 3, create_prolog_flag, PL_FA_ISO)
16331646
return f != NULL;
16341647
}
16351648

1649+
1650+
/*******************************
1651+
* PUSH / POP FLAGS *
1652+
*******************************/
1653+
1654+
/** push_prolog_flag(+Flag, +Value) is det.
1655+
1656+
Save the current thread-local value of Flag (or that it does not exist)
1657+
and set it to Value. Nestable. See pop_prolog_flag/1.
1658+
*/
1659+
1660+
/** pop_prolog_flag(+Flag) is det.
1661+
1662+
Restore the last state saved by push_prolog_flag/2. If Flag was absent
1663+
at the matching push, it is removed again. Raises existence_error if
1664+
no matching push exists on the current thread.
1665+
*/
1666+
1667+
#define put_saved_flag_value(val, saved) \
1668+
LDFUNC(put_saved_flag_value, val, saved)
1669+
1670+
static bool
1671+
put_saved_flag_value(DECL_LD term_t val, const prolog_flag *saved)
1672+
{ switch(saved->flags & FT_MASK)
1673+
{ case FT_BOOL:
1674+
case FT_ATOM:
1675+
return PL_put_atom(val, saved->value.a);
1676+
case FT_INTEGER:
1677+
return PL_put_int64(val, saved->value.i);
1678+
case FT_FLOAT:
1679+
return PL_put_float(val, saved->value.f);
1680+
case FT_TERM:
1681+
return PL_recorded(saved->value.t, val);
1682+
}
1683+
assert(0);
1684+
return false;
1685+
}
1686+
1687+
#define ensure_local_flag(k, f) \
1688+
LDFUNC(ensure_local_flag, k, f)
1689+
1690+
/* Return a thread-local prolog_flag for `k`. Unlike the copy-on-write
1691+
set_prolog_flag path, we force a local copy even when only one thread
1692+
exists: otherwise a second thread created *between* push and pop
1693+
would cause an internal set_prolog_flag to COW the flag into LD,
1694+
leaving the pushed chain stranded in the GD entry and making pop
1695+
fail to find it.
1696+
*/
1697+
static prolog_flag *
1698+
ensure_local_flag(DECL_LD atom_t k, prolog_flag *f)
1699+
{
1700+
#ifdef O_PLMT
1701+
if ( !(f->flags & FF_ISLOCAL) )
1702+
{ f = copy_prolog_flag(f);
1703+
register_local_flag(k, f);
1704+
PL_register_atom(k);
1705+
}
1706+
#endif
1707+
return f;
1708+
}
1709+
1710+
static
1711+
PRED_IMPL("push_prolog_flag", 2, push_prolog_flag, 0)
1712+
{ PRED_LD
1713+
atom_t k;
1714+
Module m = MODULE_parse;
1715+
term_t key = PL_new_term_ref();
1716+
term_t value = A2;
1717+
1718+
if ( !PL_strip_module(A1, &m, key) ||
1719+
!PL_get_atom_ex(key, &k) )
1720+
return false;
1721+
1722+
PL_LOCK(L_PLFLAG);
1723+
prolog_flag *f = NULL;
1724+
#ifdef O_PLMT
1725+
if ( LD->prolog_flag.table )
1726+
f = lookupHTableWP(LD->prolog_flag.table, k);
1727+
#endif
1728+
if ( !f )
1729+
f = lookupHTableWP(GD->prolog_flag.table, k);
1730+
1731+
bool rc;
1732+
if ( f )
1733+
{ if ( !check_flag_write_access(f, k, 0) )
1734+
{ PL_UNLOCK(L_PLFLAG);
1735+
return false;
1736+
}
1737+
f = ensure_local_flag(k, f);
1738+
prolog_flag *saved = copy_prolog_flag(f);
1739+
rc = set_flag_value(f, m, k, value);
1740+
if ( rc )
1741+
{ saved->pushed = f->pushed;
1742+
f->pushed = saved;
1743+
} else
1744+
{ clean_prolog_flag(saved);
1745+
freeHeap(saved, sizeof(*saved));
1746+
}
1747+
} else
1748+
{ prolog_flag *nf = new_prolog_flag(value, FT_FROM_VALUE);
1749+
if ( nf )
1750+
{ prolog_flag *absent = allocHeapOrHalt(sizeof(*absent));
1751+
memset(absent, 0, sizeof(*absent));
1752+
absent->flags = FT_INTEGER|FF_PUSH_ABSENT;
1753+
nf->pushed = absent;
1754+
#ifdef O_PLMT
1755+
register_local_flag(k, nf);
1756+
PL_register_atom(k);
1757+
#else
1758+
addNewHTableWP(GD->prolog_flag.table, k, nf);
1759+
PL_register_atom(k);
1760+
#endif
1761+
rc = true;
1762+
} else
1763+
{ rc = false;
1764+
}
1765+
}
1766+
PL_UNLOCK(L_PLFLAG);
1767+
1768+
return rc;
1769+
}
1770+
1771+
static
1772+
PRED_IMPL("pop_prolog_flag", 1, pop_prolog_flag, 0)
1773+
{ PRED_LD
1774+
atom_t k;
1775+
Module m = MODULE_parse;
1776+
term_t key = PL_new_term_ref();
1777+
1778+
if ( !PL_strip_module(A1, &m, key) ||
1779+
!PL_get_atom_ex(key, &k) )
1780+
return false;
1781+
1782+
PL_LOCK(L_PLFLAG);
1783+
prolog_flag *f = NULL;
1784+
bool in_local = false;
1785+
#ifdef O_PLMT
1786+
if ( LD->prolog_flag.table )
1787+
{ f = lookupHTableWP(LD->prolog_flag.table, k);
1788+
in_local = (f != NULL);
1789+
}
1790+
#endif
1791+
if ( !f )
1792+
f = lookupHTableWP(GD->prolog_flag.table, k);
1793+
1794+
if ( !f || !f->pushed )
1795+
{ PL_UNLOCK(L_PLFLAG);
1796+
term_t ex = PL_new_term_ref();
1797+
return ( PL_put_atom(ex, k) &&
1798+
PL_error(NULL, 0, NULL, ERR_EXISTENCE,
1799+
ATOM_pushed_flag, ex) );
1800+
}
1801+
1802+
prolog_flag *prev = f->pushed;
1803+
bool rc;
1804+
1805+
if ( prev->flags & FF_PUSH_ABSENT )
1806+
{ f->pushed = NULL;
1807+
#ifdef O_PLMT
1808+
if ( in_local )
1809+
deleteHTableWP(LD->prolog_flag.table, k);
1810+
else
1811+
#endif
1812+
deleteHTableWP(GD->prolog_flag.table, k);
1813+
clean_prolog_flag(f);
1814+
freeHeap(f, sizeof(*f));
1815+
PL_unregister_atom(k);
1816+
freeHeap(prev, sizeof(*prev));
1817+
rc = true;
1818+
} else
1819+
{ term_t restore = PL_new_term_ref();
1820+
rc = put_saved_flag_value(restore, prev);
1821+
if ( rc )
1822+
{ f->pushed = prev->pushed;
1823+
prev->pushed = NULL;
1824+
rc = set_flag_value(f, m, k, restore);
1825+
clean_prolog_flag(prev);
1826+
freeHeap(prev, sizeof(*prev));
1827+
}
1828+
}
1829+
PL_UNLOCK(L_PLFLAG);
1830+
1831+
return rc;
1832+
}
1833+
1834+
16361835
bool
16371836
PL_current_prolog_flag(atom_t name, int type, void *value)
16381837
{ prolog_flag *f;
@@ -2598,4 +2797,6 @@ BeginPredDefs(prologflag)
25982797
PRED_DEF("create_prolog_flag", 3, create_prolog_flag, 0)
25992798
PRED_DEF("current_prolog_flag", 2, current_prolog_flag, PL_FA_ISO|NDET)
26002799
PRED_DEF("$current_prolog_flag", 5, dcurrent_prolog_flag, NDET)
2800+
PRED_DEF("push_prolog_flag", 2, push_prolog_flag, 0)
2801+
PRED_DEF("pop_prolog_flag", 1, pop_prolog_flag, 0)
26012802
EndPredDefs

src/os/pl-prologflag.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141

4242
#define FF_ISLOCAL 0x040000 /* Flag is local (internal) */
4343
#define FF_ISCREATE 0x080000 /* Called from create_prolog_flag/3 */
44-
#define FF_MASK 0x0ff000 /* All FF flags */
44+
#define FF_PUSH_ABSENT 0x100000 /* Pushed frame: flag was absent */
45+
#define FF_MASK 0x1ff000 /* All FF flags */
4546
static_assert(FF_ISLOCAL == FF_GLOBAL<<1,
4647
"Private flags must follow public flags");
4748

@@ -61,6 +62,7 @@ typedef struct _prolog_flag
6162
record_t t; /* value as term */
6263
} value;
6364
oneof *oneof;
65+
struct _prolog_flag *pushed; /* push_prolog_flag/2 stack */
6466
} prolog_flag;
6567

6668
/*******************************

0 commit comments

Comments
 (0)