mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-01-12 22:40:46 -08:00
360 lines
13 KiB
ReStructuredText
360 lines
13 KiB
ReStructuredText
.. sources:
|
|
|
|
<https://info.ravenbrook.com/project/mps/doc/2002-06-18/obsolete-mminfo/mmdoc/doc/mps/guide/ld/index.html>`_
|
|
|
|
|
|
.. index::
|
|
single: location dependency
|
|
single: dependency; location
|
|
|
|
.. _topic-location:
|
|
|
|
Location dependency
|
|
===================
|
|
|
|
Location dependencies provide a means by which the :term:`client
|
|
program` can depend on the :dfn:`location` of blocks (that is, on the
|
|
bits in pointers to the blocks) in the presence of a :term:`moving
|
|
memory manager` (where the location of blocks may change and the
|
|
client program needs to recognize and correctly deal with such cases).
|
|
|
|
The interface is intended to support (amongst other things)
|
|
address-based hash tables and that will be used as a running example.
|
|
See the section :ref:`guide-advanced-location` in the Guide for a more
|
|
detailed look at this example.
|
|
|
|
|
|
Terminology
|
|
-----------
|
|
|
|
A :dfn:`location dependency` is represented by an structure of type
|
|
:c:type:`mps_ld_s`. It encapsulates a set of dependencies on the
|
|
locations of blocks. It can be used to determine whether any of the
|
|
blocks have been moved by the memory manager.
|
|
|
|
To :dfn:`depend` on the location of a block is to perform a computation
|
|
whose result depends on the particular representation (that is, the
|
|
"bit-pattern") of a reference to the block. This includes any sort of
|
|
hash operation on a pointer to the block (such as treating the
|
|
pointer as an integer and taking it modulo 257). It is possible to
|
|
depend on the location of more than one block.
|
|
|
|
A dependency has been made :dfn:`stale` if the block whose location was
|
|
depended on might have moved since the dependency was made. If this is
|
|
the case, then computations that depend on the location of a block
|
|
may give different results. A location dependency has been made stale
|
|
if any of the blocks whose location has been depended on might have
|
|
moved since the respective dependency was made.
|
|
|
|
|
|
.. index::
|
|
single: location dependency; creating
|
|
|
|
Creating dependencies
|
|
---------------------
|
|
|
|
The :term:`client program` must provide space for the
|
|
:c:type:`mps_ld_s` structure. Typically, this will be inlined in some
|
|
larger structure. This structure can be in memory managed by the MPS
|
|
or elsewhere; that doesn't matter.
|
|
|
|
For example, the toy Scheme interpreter inlines the location
|
|
dependency in its hash table structure:
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 5
|
|
|
|
typedef struct table_s {
|
|
type_t type; /* TYPE_TABLE */
|
|
hash_t hash; /* hash function */
|
|
cmp_t cmp; /* comparison function */
|
|
mps_ld_s ld; /* location dependency */
|
|
obj_t buckets; /* hash buckets */
|
|
} table_s;
|
|
|
|
Before the first use, the location dependency must be reset by calling
|
|
the function :c:func:`mps_ld_reset`.
|
|
|
|
.. note::
|
|
|
|
This means that it is not possible to statically create a location
|
|
dependency that has been reset.
|
|
|
|
You can call :c:func:`mps_ld_reset` at any later point to clear all
|
|
dependencies from the structure. For example, this is normally done
|
|
whenever :c:func:`mps_ld_isstale` returns true.
|
|
|
|
|
|
.. index::
|
|
single: location dependency; adding
|
|
|
|
Adding dependencies
|
|
-------------------
|
|
|
|
*Before* the location of a block is depended on (for example,
|
|
hashed) a reference to the block may be added to a location
|
|
dependency by calling :c:func:`mps_ld_add`. Dependencies on many
|
|
blocks can be added to the same location dependency.
|
|
|
|
It is also possible to merge two location dependencies by calling
|
|
:c:func:`mps_ld_merge`, which has the same effect as adding all of the
|
|
references from one dependency to another.
|
|
|
|
For example, in an address-based hash table implementation, each key
|
|
that is added to the table must be added to the dependency before its
|
|
address is hashed. In the toy Scheme interpreter this is most easily
|
|
done in the function that hashes an address:
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 4
|
|
|
|
static unsigned long eq_hash(obj_t obj, mps_ld_t ld)
|
|
{
|
|
union {char s[sizeof(obj_t)]; obj_t addr;} u;
|
|
if (ld) mps_ld_add(ld, arena, obj);
|
|
u.addr = obj;
|
|
return hash(u.s, sizeof(obj_t));
|
|
}
|
|
|
|
|
|
.. index::
|
|
single: location dependency; testing staleness
|
|
single: staleness; testing
|
|
|
|
Testing dependencies for staleness
|
|
----------------------------------
|
|
|
|
When the locations of blocks are used (during a hash table lookup for
|
|
example), the computation should be carried out and the result used in
|
|
the usual way (for example, the pointer is hashed and the has used to
|
|
index into the table). At this point one of three situations can
|
|
occur:
|
|
|
|
1. success (for example, the key was found in the table at the place
|
|
indicated by the hash of its address);
|
|
|
|
2. failure: the location of these blocks has not been depended on
|
|
before (for example, the key has never been added to the hash
|
|
table);
|
|
|
|
3. failure: the location of these blocks has been depended on before,
|
|
but the one or more of the blocks has moved and the dependency has
|
|
been made stale (in this case the table would need to be rehashed
|
|
and the lookup repeated).
|
|
|
|
Success requires no further test: the operation can proceed. In case
|
|
of failure, you should call :c:func:`mps_ld_isstale`. If it returns
|
|
false, then no blocks have moved, so you must be in case (2).
|
|
|
|
But if :c:func:`mps_ld_isstale` returns true, you could still be in
|
|
either case (2) or case (3). All :c:func:`mps_ld_isstale` tells you is
|
|
that some blocks that have been depended on might have moved. At this
|
|
point you need to:
|
|
|
|
1. reset the location dependency;
|
|
|
|
2. repeat the computation in some way that doesn't depend on the
|
|
old locations of the blocks; and
|
|
|
|
3. re-add a dependency on each block.
|
|
|
|
For example, in the case of a hash table you should rehash based on
|
|
the new locations of the blocks.
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 6
|
|
|
|
static obj_t table_ref(obj_t tbl, obj_t key)
|
|
{
|
|
struct bucket_s *b = buckets_find(tbl, tbl->table.buckets, key, NULL);
|
|
if (b && b->key != NULL && b->key != obj_deleted)
|
|
return b->value;
|
|
if (mps_ld_isstale(&tbl->table.ld, arena, key)) {
|
|
b = table_rehash(tbl, tbl->table.buckets->buckets.length, key);
|
|
if (b) return b->value;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
After :c:func:`mps_ld_isstale` has returned true, and you've rehashed
|
|
the table, it might be tempting to repeat the usual address-based
|
|
lookup. But the MPS does not guarantee that :c:func:`mps_ld_isstale`
|
|
will not return true again: if the re-hashing took a long time or
|
|
touched lots of memory, there might have been another garbage
|
|
collection. (The only time that :c:func:`mps_ld_isstale` guarantees to
|
|
return false is immediately after :c:func:`mps_ld_reset`.)
|
|
|
|
You might put in a loop here, but for reliability it is better to fall
|
|
back to a non-address-based version of the computation: here, since
|
|
``table_rehash`` has to loop over all the entries in the table anyway,
|
|
it might as well find the bucket containing ``key`` at the same time
|
|
and return it.
|
|
|
|
|
|
.. index::
|
|
pair: location dependency; thread safety
|
|
|
|
Thread safety
|
|
-------------
|
|
|
|
The functions are all thread-safe with respect to operations on
|
|
different location dependencies. That means that it is not necessary
|
|
for threads to interlock if they are performing operations on
|
|
different location dependencies. The descriptions of the individual
|
|
functions detail their thread-safety attributes if multiple threads
|
|
need to access the same location dependency.
|
|
|
|
|
|
.. index::
|
|
single: location dependency; interface
|
|
|
|
Location dependency interface
|
|
-----------------------------
|
|
|
|
.. c:type:: mps_ld_t
|
|
|
|
The type of :term:`location dependencies`. It is a
|
|
:term:`transparent alias <transparent type>` for a pointer to
|
|
:c:type:`mps_ld_s`.
|
|
|
|
A location dependency records the fact that the :term:`client
|
|
program` depends on the bit patterns of some :term:`references`
|
|
(and not merely on the identity of the :term:`block` to which the
|
|
reference refers), and provides a function
|
|
(:c:func:`mps_ld_isstale`) to find out whether any of these
|
|
references have been changed because a block has been :term:`moved
|
|
<moving garbage collector>`.
|
|
|
|
A typical use is in the implementation of a hash table which
|
|
hashes blocks by hashing their addresses. After a block has moved,
|
|
the table needs to be rehashed, otherwise it will not be
|
|
found in the table.
|
|
|
|
|
|
.. c:type:: mps_ld_s
|
|
|
|
The type of the structure used to represent a :term:`location
|
|
dependency`. ::
|
|
|
|
typedef struct mps_ld_s {
|
|
mps_word_t w0, w1;
|
|
} mps_ld_s;
|
|
|
|
It is an opaque structure type: it is supplied so that the
|
|
:term:`client program` can inline the structure (because its size
|
|
is known), but the client must not access it other than via the
|
|
functions :c:func:`mps_ld_add`, :c:func:`mps_ld_isstale`,
|
|
:c:func:`mps_ld_merge`, and :c:func:`mps_ld_reset`.
|
|
|
|
|
|
.. c:function:: void mps_ld_add(mps_ld_t ld, mps_arena_t arena, mps_addr_t addr)
|
|
|
|
Add a dependency on a :term:`block` to a :term:`location
|
|
dependency`.
|
|
|
|
``ld`` is a location dependency.
|
|
|
|
``arena`` is the :term:`arena` to which ``addr`` belongs.
|
|
|
|
``addr`` is the address of the block.
|
|
|
|
After calling :c:func:`mps_ld_add`, and until ``ld`` is passed to
|
|
:c:func:`mps_ld_reset`, the call ::
|
|
|
|
mps_ld_isstale(ld, arena, addr)
|
|
|
|
will return true if the block has moved.
|
|
|
|
.. note::
|
|
|
|
It is an error to call :c:func:`mps_ld_add` on the same
|
|
location dependency with addresses from two different arenas.
|
|
If you need to test for staleness against multiple arenas,
|
|
then you need at least one location dependency for each arena.
|
|
|
|
:c:func:`mps_ld_add` is not thread-safe with respect to
|
|
:c:func:`mps_ld_add`, :c:func:`mps_ld_merge`, or
|
|
:c:func:`mps_ld_reset` on the same location dependency, but it
|
|
is thread-safe with respect to :c:func:`mps_ld_isstale`
|
|
operations. This means that calls to :c:func:`mps_ld_add` from
|
|
different :term:`threads` must interlock if they are
|
|
using the same location dependency. The practical upshot of
|
|
this is that there should be a lock associated with each
|
|
location dependency.
|
|
|
|
|
|
.. c:function:: mps_bool_t mps_ld_isstale(mps_ld_t ld, mps_arena_t arena, mps_addr_t addr)
|
|
|
|
Determine if any of the dependencies in a :term:`location
|
|
dependency` are stale with respect to an :term:`arena`.
|
|
|
|
``ld`` is the location dependency.
|
|
|
|
``arena`` is the arena to test for staleness against. It must be
|
|
the same arena that was passed to all calls to
|
|
:c:func:`mps_ld_add` on ``ld``.
|
|
|
|
``addr`` is an address that may appear in :term:`telemetry
|
|
<telemetry stream>` events related to this call (it will *not* be
|
|
tested for staleness).
|
|
|
|
The location dependency is examined to determine whether any of
|
|
the dependencies encapsulated in it have been made stale with
|
|
respect to ``arena``. If any of the dependencies encapsulated in
|
|
the location dependency are stale (that is, the blocks whose
|
|
location has been depended on have been moved by ``arena``) then
|
|
:c:func:`mps_ld_isstale` will return true. If there have been no
|
|
calls to :c:func:`mps_ld_add` on ``ld`` since the last call to
|
|
:c:func:`mps_ld_reset`, then :c:func:`mps_ld_isstale` will return
|
|
false. :c:func:`mps_ld_isstale` may return any value in other
|
|
circumstances (but will strive to return false if the blocks
|
|
encapsulated in the location dependency have not moved).
|
|
|
|
.. note::
|
|
|
|
:c:func:`mps_ld_isstale` may report a false positive
|
|
(returning true despite none of the added addresses having
|
|
being moved by the arena) but never a false negative
|
|
(returning false when an added address has been moved).
|
|
|
|
:c:func:`mps_ld_isstale` is thread-safe with respect to itself
|
|
and with respect to :c:func:`mps_ld_add`, but not with respect
|
|
to :c:func:`mps_ld_reset`.
|
|
|
|
|
|
.. c:function:: void mps_ld_merge(mps_ld_t dest_ld, mps_arena_t arena, mps_ld_t src_ld)
|
|
|
|
Merge one :term:`location dependency` into another.
|
|
|
|
``dest_ld`` is the destination of the merge.
|
|
|
|
``arena`` is the :term:`arena` .
|
|
|
|
``src_ld`` is the source of the merge.
|
|
|
|
The effect of this is to add all the addresses that were added to
|
|
``src_ld`` to the ``dest_ld``.
|
|
|
|
.. note::
|
|
|
|
:c:func:`mps_ld_merge` has the same thread-safety properties
|
|
as :c:func:`mps_ld_add`.
|
|
|
|
|
|
.. c:function:: void mps_ld_reset(mps_ld_t ld, mps_arena_t arena)
|
|
|
|
Reset a :term:`location dependency`.
|
|
|
|
``ld`` is the location dependency.
|
|
|
|
``arena`` is an arena.
|
|
|
|
After this call, ``ld`` encapsulates no dependencies. After the
|
|
call to :c:func:`mps_ld_reset` and prior to any call to
|
|
:c:func:`mps_ld_add` on ``ld``, :c:func:`mps_ld_isstale` on ``ld``
|
|
will return false for all arenas.
|
|
|
|
.. note::
|
|
|
|
:c:func:`mps_ld_reset` is not thread-safe with respect to any
|
|
other location dependency function.
|