.. highlight:: none
.. index::
pair: lands; design
.. _design-land:
Lands
=====
.. mps:prefix:: design.mps.land
Introduction
------------
:mps:tag:`intro` This is the design of the *land* abstract data type, which
represents a collection of contiguous address ranges.
:mps:tag:`readership` This document is intended for any MPS developer.
:mps:tag:`source` design.mps.cbs_, design.mps.freelist_.
:mps:tag:`overview` Collections of address ranges are used in several places
in the MPS: the arena stores a set of mapped address ranges; pools
store sets of address ranges which have been acquired from the arena
and sets of address ranges that are available for allocation. The
*land* abstract data type makes it easy to try out different
implementations with different performance characteristics and other
attributes.
:mps:tag:`name` The name is inspired by *rangeland* meaning *group of
ranges* (where *ranges* is used in the sense *grazing areas*).
Definitions
-----------
:mps:tag:`def.range` A (contiguous) *range* of addresses is a semi-open
interval on address space.
:mps:tag:`def.isolated` A contiguous range is *isolated* with respect to
some property it has, if adjacent elements do not have that property.
Requirements
------------
:mps:tag:`req.set` Must maintain a set of addresses.
:mps:tag:`req.add` Must be able to add address ranges to the set.
:mps:tag:`req.remove` Must be able to remove address ranges from the set.
:mps:tag:`req.size` Must report concisely to the client when isolated
contiguous ranges of at least a certain size appear and disappear.
:mps:tag:`req.iterate` Must support the iteration of all isolated
contiguous ranges.
:mps:tag:`req.protocol` Must detect protocol violations.
:mps:tag:`req.debug` Must support debugging of client code.
:mps:tag:`req.align` Must support an alignment (the alignment of all
addresses specifying ranges) of down to ``sizeof(void *)`` without
losing memory.
Interface
---------
Types
.....
.. c:type:: LandStruct *Land
:mps:tag:`type.land` The type of a generic land instance.
.. c:type:: Bool (*LandVisitor)(Land land, Range range, void *closure)
:mps:tag:`type.visitor` Type ``LandVisitor`` is a callback function that may
be passed to :c:func:`LandIterate()`. It is called for every isolated
contiguous range in address order. The function must return a :c:type:`Bool`
indicating whether to continue with the iteration.
.. c:type:: Bool (*LandDeleteVisitor)(Bool *deleteReturn, Land land, Range range, void *closure)
:mps:tag:`type.deletevisitor` Type ``LandDeleteVisitor`` is a callback function that may
be passed to :c:func:`LandIterateAndDelete()`. It is called for every isolated
contiguous range in address order. The function must return a :c:type:`Bool`
indicating whether to continue with the iteration. It may additionally
update ``*deleteReturn`` to :c:macro:`TRUE` if the range must be deleted from
the land, or :c:macro:`FALSE` if the range must be kept. (The default is to
keep the range.)
Generic functions
.................
.. c:function:: Res LandInit(Land land, LandClass class, Arena arena, Align alignment, void *owner, ArgList args)
:mps:tag:`function.init` :c:func:`LandInit()` initializes the land structure for
the given class. The land will perform allocation (if necessary -- not
all land classes need to allocate) in the supplied arena. The
``alignment`` parameter is the alignment of the address ranges that
will be stored and retrieved from the land. The parameter ``owner`` is
output as a parameter to the ``LandInit`` event. The newly initialized
land contains no ranges.
.. c:function:: Res LandCreate(Land *landReturn, Arena arena, LandClass class, Align alignment, void *owner, ArgList args)
:mps:tag:`function.create` :c:func:`LandCreate()` allocates memory for a land
structure of the given class in ``arena``, and then passes all
parameters to :c:func:`LandInit()`.
.. c:function:: void LandFinish(Land land)
:mps:tag:`function.finish` :c:func:`LandFinish()` finishes the land structure and
discards any other resources associated with the land.
.. c:function:: void LandSize(Land land)
:mps:tag:`function.size` :c:func:`LandSize()` returns the total size of the ranges
stored in the land.
.. c:function:: Res LandInsert(Range rangeReturn, Land land, Range range)
:mps:tag:`function.insert` If any part of ``range`` is already in the land,
then leave the land unchanged and return ``ResFAIL``. Otherwise,
attempt to insert ``range`` into the land. If the insertion succeeds,
then update ``rangeReturn`` to describe the contiguous isolated range
containing the inserted range (this may differ from ``range`` if there
was coalescence on either side) and return ``ResOK``. If the insertion
fails, return a result code indicating allocation failure.
:mps:tag:`function.insert.fail` Insertion of a valid range (that is, one
that does not overlap with any range in the land) can only fail if the
new range is isolated and the allocation of the necessary data
structure to represent it failed.
:mps:tag:`function.insert.alias` It is acceptable for ``rangeReturn`` and
``range`` to share storage.
.. c:function:: Res LandInsertSteal(Range rangeReturn, Land land, Range rangeIO)
:mps:tag:`function.insert-steal` If any part of ``rangeIO`` is already in
the land, then leave the land unchanged and return ``ResFAIL``.
Otherwise, insert ``rangeIO`` into the land, update ``rangeReturn`` to
describe the contiguous isolated range containing the inserted range
(this may differ from ``range`` if there was coalescence on either
side), and return ``ResOK``.
:mps:tag:`function.insert-steal.steal` If insertion requires allocation for
the land's internal data structures, steal some of the memory in
``rangeIO``, use it to satisfy the allocation, update ``rangeIO`` so
that it describes the remaining part of of the range, and insert the
remainder into the land as described above.
:mps:tag:`function.insert-steal.allocated` In order for stealing to work,
the inserted range must be allocated from the arena to some pool or
pools.
:mps:tag:`function.insert-steal.empty` After stealing memory, ``rangeIO``
might be empty, in which case ``rangeReturn`` will be a copy of
``rangeIO``.
:mps:tag:`function.insert-steal.alias.not` It is not acceptable for
``rangeReturn`` and ``rangeIO`` to share storage.
.. c:function:: Res LandDelete(Range rangeReturn, Land land, Range range)
:mps:tag:`function.delete` If any part of the range is not in the land,
then leave the land unchanged and return ``ResFAIL``. Otherwise, update
``rangeReturn`` to describe the contiguous isolated range that
contains ``range`` (this may differ from ``range`` if there are
fragments on either side) and attempt to delete the range from the
land. If the deletion succeeds, return ``ResOK``. If the deletion
fails, return a result code indicating allocation failure.
:mps:tag:`function.delete.fail` Deletion of a valid range (that is, one
that is wholly contained in the land) can only fail if there are
fragments on both sides and the allocation of the necessary data
structures to represent them fails.
:mps:tag:`function.delete.return` :c:func:`LandDelete()` returns the contiguous
isolated range that contains ``range`` even if the deletion fails.
This is so that the caller can try deleting the whole block (which is
guaranteed to succeed) and managing the fragments using a fallback
strategy.
:mps:tag:`function.delete.alias` It is acceptable for ``rangeReturn`` and
``range`` to share storage.
.. c:function:: Res LandDeleteSteal(Range rangeReturn, Land land, Range range)
:mps:tag:`function.delete-steal` If any part of the range is not in the
land, then leave the land unchanged and return ``ResFAIL``. Otherwise,
update ``rangeReturn`` to describe the contiguous isolated range that
contains ``range`` (this may differ from ``range`` if there are
fragments on either side), delete the range from the land, and return
``ResOK``.
:mps:tag:`function.delete-steal.steal` If deletion requires allocation for
the land's internal data structures, steal some of the memory in the
contiguous isolated range that contains ``range``, and use it to
satisfy the allocation.
:mps:tag:`function.delete-steal.allocated` In order for stealing to work,
the addresses stored in the land must be allocated from the arena to
some pool or pools.
:mps:tag:`function.delete-steal.alias` It is acceptable for ``rangeReturn``
and ``range`` to share storage.
.. c:function:: Bool LandIterate(Land land, LandVisitor visitor, void *closure)
:mps:tag:`function.iterate` :c:func:`LandIterate()` is the function used to
iterate all isolated contiguous ranges in a land. It receives a
visitor function to invoke on every range, and a closure pointer
to pass on to the visitor function. If the visitor
function returns :c:macro:`FALSE`, then iteration is terminated and
:c:func:`LandIterate()` returns :c:macro:`FALSE`. If all iterator method calls
return :c:macro:`TRUE`, then :c:func:`LandIterate()` returns :c:macro:`TRUE`
.. c:function:: Bool LandIterateAndDelete(Land land, LandDeleteVisitor visitor, void *closure)
:mps:tag:`function.iterate.and.delete` As :c:func:`LandIterate()`, but the visitor
function additionally returns a Boolean indicating whether the range
should be deleted from the land.
:mps:tag:`function.iterate.and.delete.justify` The reason for having both
:c:func:`LandIterate()` and :c:func:`LandIterateAndDelete()` is that it may be
possible to use a more efficient algorithm, or to preserve more
properties of the data structure, when it is known that the land will
not be modified during the iteration. For example, in the CBS
implementation, :c:func:`LandIterate()` uses :c:func:`TreeTraverse()` which
preserves the tree structure, whereas :c:func:`LandIterateAndDelete()` uses
:c:func:`TreeTraverseAndDelete()` which flattens the tree structure, losing
information about recently accessed nodes.
.. c:function:: Bool LandFindFirst(Range rangeReturn, Range oldRangeReturn, Land land, Size size, FindDelete findDelete)
:mps:tag:`function.find.first` Locate the first block (in address order)
within the land of at least the specified size, update ``rangeReturn``
to describe that range, and return :c:macro:`TRUE`. If there is no such
block, it returns :c:macro:`FALSE`.
In addition, optionally delete the top, bottom, or all of the found
range, depending on the ``findDelete`` argument. This saves a separate
call to :c:func:`LandDelete()`, and uses the knowledge of exactly where we
found the range. The value of ``findDelete`` must come from this
enumeration::
enum {
FindDeleteNONE, /* don't delete after finding */
FindDeleteLOW, /* delete size bytes from low end of block */
FindDeleteHIGH, /* delete size bytes from high end of block */
FindDeleteENTIRE /* delete entire range */
};
The original contiguous isolated range in which the range was found is
returned via the ``oldRangeReturn`` argument. (If ``findDelete`` is
``FindDeleteNONE`` or ``FindDeleteENTIRE``, then this will be
identical to the range returned via the ``rangeReturn`` argument.)
.. c:function:: Bool LandFindLast(Range rangeReturn, Range oldRangeReturn, Land land, Size size, FindDelete findDelete)
:mps:tag:`function.find.last` Like :c:func:`LandFindFirst()`, except that it
finds the last block in address order.
.. c:function:: Bool LandFindLargest(Range rangeReturn, Range oldRangeReturn, Land land, Size size, FindDelete findDelete)
:mps:tag:`function.find.largest` Locate the largest block within the
land, and if that block is at least as big as ``size``, return its
range via the ``rangeReturn`` argument, and return :c:macro:`TRUE`. If there
are no blocks in the land at least as large as ``size``, return
:c:macro:`FALSE`. Pass 0 for ``size`` if you want the largest block
unconditionally.
Like :c:func:`LandFindFirst()`, optionally delete the range (specifying
``FindDeleteLOW`` or ``FindDeleteHIGH`` has the same effect as
``FindDeleteENTIRE``), and return the original contiguous isolated
range in which the range was found via the ``oldRangeReturn``
argument.
.. c:function:: Res LandFindInZones(Bool *foundReturn, Range rangeReturn, Range oldRangeReturn, Land land, Size size, ZoneSet zoneSet, Bool high)
:mps:tag:`function.find.zones` Locate a block at least as big as ``size``
that lies entirely within the ``zoneSet``, return its range via the
``rangeReturn`` argument, set ``*foundReturn`` to :c:macro:`TRUE`, and return
``ResOK``. (The first such block, if ``high`` is :c:macro:`FALSE`, or the
last, if ``high`` is :c:macro:`TRUE`.) If there is no such block, set
``*foundReturn`` to :c:macro:`FALSE`, and return ``ResOK``.
Delete the range as for :c:func:`LandFindFirst()` and :c:func:`LastFindLast()`
(with the effect of ``FindDeleteLOW`` if ``high`` is :c:macro:`FALSE` and the
effect of ``FindDeleteHIGH`` if ``high`` is :c:macro:`TRUE`), and return the
original contiguous isolated range in which the range was found via
the ``oldRangeReturn`` argument.
:mps:tag:`function.find.zones.fail` It's possible that the range can't be
deleted from the land because that would require allocation, in which
case the result code indicates the cause of the failure.
.. c:function:: Res LandDescribe(Land land, mps_lib_FILE *stream)
:mps:tag:`function.describe` :c:func:`LandDescribe()` prints a textual
representation of the land to the given stream. It is provided for
debugging purposes only.
.. c:function:: void LandFlush(Land dest, Land src)
:mps:tag:`function.flush` Delete ranges of addresses from ``src`` and insert
them into ``dest``, so long as :c:func:`LandInsert()` remains successful.
Implementations
---------------
There are three land implementations:
#. CBS (Coalescing Block Structure) stores ranges in a splay tree. It
has fast (logarithmic in the number of ranges) insertion, deletion
and searching, but has substantial space overhead. See
design.mps.cbs_.
#. Freelist stores ranges in an address-ordered free list, as in
traditional :c:func:`malloc()` implementations. Insertion, deletion, and
searching are slow (proportional to the number of ranges) but it
does not need to allocate. See design.mps.freelist_.
#. Failover combines two lands, using one (the *primary*) until it
fails, and then falls back to the other (the *secondary*). See
design.mps.failover_.
.. _design.mps.cbs: cbs.html
.. _design.mps.freelist: freelist.html
.. _design.mps.failover: failover.html
Testing
-------
:mps:tag:`test` There is a stress test for implementations of this interface
in impl.c.landtest. This allocates a large block of memory and then
simulates the allocation and deallocation of ranges within this block
using both a :c:type:`Land` and a :c:type:`BT`. It makes both valid and invalid
requests, and compares the :c:type:`Land` response to the correct behaviour
as determined by the :c:type:`BT`. It iterates the ranges in the :c:type:`Land`,
comparing them to the :c:type:`BT`. It invokes the :c:func:`LandDescribe()`
generic function, but makes no automatic test of the resulting output.