mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-01-08 12:40:49 -08:00
Add description of the object format to the language guide.
Copied from Perforce Change: 179906 ServerID: perforce.ravenbrook.com
This commit is contained in:
parent
15505d7649
commit
e07d0badc8
9 changed files with 659 additions and 142 deletions
|
|
@ -204,8 +204,8 @@ Memory Management Glossary: C
|
|||
|
||||
A :term:`formatted object` that contains data from the
|
||||
:term:`client program`. One of three types of formatted
|
||||
objects, the other two being :term:`forwarded objects
|
||||
<forwarded object>` and :term:`padding objects <padding
|
||||
objects, the other two being :term:`forwarding objects
|
||||
<forwarding object>` and :term:`padding objects <padding
|
||||
object>`.
|
||||
|
||||
client program
|
||||
|
|
|
|||
|
|
@ -272,16 +272,8 @@ Memory Management Glossary: F
|
|||
object with a :term:`forwarding marker` that points to the
|
||||
new location of the object. See :c:type:`mps_fmt_fwd_t`.
|
||||
|
||||
forwarded object
|
||||
|
||||
.. mps:specific::
|
||||
|
||||
A :term:`formatted object` that has been replaced by a
|
||||
:term:`forwarding marker`. One of three types of formatted
|
||||
objects, the other two being :term:`client objects <client
|
||||
object>` and :term:`padding objects <padding object>`.
|
||||
|
||||
forwarding marker
|
||||
forwarding object
|
||||
forwarding pointer
|
||||
|
||||
Some :term:`garbage collectors <garbage collector>`
|
||||
|
|
@ -295,7 +287,13 @@ Memory Management Glossary: F
|
|||
|
||||
.. seealso:: :term:`copying garbage collection`, :term:`two-space collector`.
|
||||
|
||||
.. mps:specific:: The term *forwarding marker* is used.
|
||||
.. mps:specific::
|
||||
|
||||
The term *forwarding object* is used. This is a
|
||||
:term:`formatted object` that has been replaced by a
|
||||
:term:`forwarding marker`. One of three types of formatted
|
||||
objects, the other two being :term:`client objects <client
|
||||
object>` and :term:`padding objects <padding object>`.
|
||||
|
||||
fragmentation
|
||||
|
||||
|
|
|
|||
|
|
@ -345,6 +345,6 @@ Memory Management Glossary: I
|
|||
A :term:`format method` that is called by a :term:`moving
|
||||
<moving garbage collector>` :term:`pool <pool>` to
|
||||
determine if a :term:`formatted object` is a
|
||||
:term:`forwarded object`, and if so, to return the address
|
||||
:term:`forwarding object`, and if so, to return the address
|
||||
where the object was moved to. See
|
||||
:c:type:`mps_fmt_isfwd_t`.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Memory Management Glossary: P
|
|||
A :term:`formatted object` that consists of
|
||||
:term:`padding`. One of three types of formatted objects,
|
||||
the other two being :term:`client objects <client object>`
|
||||
and :term:`forwarded objects <forwarded object>`.
|
||||
and :term:`forwarding objects <forwarding object>`.
|
||||
|
||||
page
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ garbage collection to the runtime system for a programming language.
|
|||
|
||||
I'm assuming that you've downloaded and compiled the MPS (see
|
||||
:ref:`guide-install`), and that you are familiar with the overall
|
||||
architecture of the MPS (:ref:`guide-overview`).
|
||||
architecture of the MPS (see :ref:`guide-overview`).
|
||||
|
||||
|
||||
------------------
|
||||
|
|
@ -94,12 +94,23 @@ operate on objects generically, testing ``TYPE(obj)`` as necessary. For example,
|
|||
}
|
||||
}
|
||||
|
||||
Each constructor (for example ``make_pair`` is the constructor for
|
||||
pairs) allocates memory for the new object by calling ``malloc``,
|
||||
but objects do not get freed, because they have :term:`indefinite
|
||||
extent`, so it is necessary to prove that they are :term:`dead` before
|
||||
their memory can be :term:`reclaimed <reclaim>`. And that task falls
|
||||
to the :term:`garbage collector`.
|
||||
Each constructor allocates memory for the new object by calling
|
||||
``malloc``. For example ``make_pair`` is the constructor for pairs::
|
||||
|
||||
static obj_t make_pair(obj_t car, obj_t cdr)
|
||||
{
|
||||
obj_t obj = (obj_t)malloc(sizeof(pair_s));
|
||||
if (obj == NULL) error("out of memory");
|
||||
total += sizeof(pair_s);
|
||||
obj->pair.type = TYPE_PAIR;
|
||||
CAR(obj) = car;
|
||||
CDR(obj) = cdr;
|
||||
return obj;
|
||||
}
|
||||
|
||||
Objects do not get freed, because it is necessary to prove that they
|
||||
are :term:`dead` before their memory can be :term:`reclaimed
|
||||
<reclaim>`. And that task falls to the :term:`garbage collector`.
|
||||
|
||||
|
||||
-----------------------
|
||||
|
|
@ -191,21 +202,20 @@ these features of the MPS.
|
|||
classes.
|
||||
|
||||
|
||||
-----------------
|
||||
The object format
|
||||
-----------------
|
||||
-----------------------
|
||||
Describing your objects
|
||||
-----------------------
|
||||
|
||||
In order for the MPS to be able to collect your objects, you need to
|
||||
In order for the MPS to be able to manage your objects, you need to
|
||||
tell it how to perform various operations on those objects, which you
|
||||
do by creating a set of :term:`format method <format method>`, and
|
||||
using them to specify an :term:`object format`. Here's the code for
|
||||
creating the object format for the Scheme interpreter::
|
||||
do by creating an :term:`object format`. Here's the code for creating
|
||||
the object format for the Scheme interpreter::
|
||||
|
||||
struct mps_fmt_A_s obj_fmt_s = {
|
||||
sizeof(mps_word_t),
|
||||
obj_scan,
|
||||
obj_skip,
|
||||
obj_copy,
|
||||
NULL,
|
||||
obj_fwd,
|
||||
obj_isfwd,
|
||||
obj_pad,
|
||||
|
|
@ -223,7 +233,8 @@ belonging to this format. The Scheme interpreter needs its objects to
|
|||
be allocated at addresses which are multiples of the machine's word
|
||||
size.
|
||||
|
||||
The other elements of the structure are the format methods, which are described in the following sections.
|
||||
The other elements of the structure are the :term:`format methods
|
||||
<format method>`, which are described in the following sections.
|
||||
|
||||
.. topics::
|
||||
|
||||
|
|
@ -234,11 +245,424 @@ The other elements of the structure are the format methods, which are described
|
|||
The scan method
|
||||
---------------
|
||||
|
||||
The :term:`scan method` is a function of type
|
||||
:c:type:`mps_fmt_scan_t`. It is called by the MPS to :term:`scan` a
|
||||
block of memory. Its task is to identify all references within the
|
||||
objects in the block of memory, and "fix" them, by calling the macros
|
||||
:c:func:`MPS_FIX1` and :c:func:`MPS_FIX2` on each reference (usually
|
||||
via the convenience macro :c:func:`MPS_FIX12`).
|
||||
|
||||
"Fixing" is a generic operation whose effect depends on the context in
|
||||
which the scan method was called. The scan method is called to
|
||||
discover references and so determine which objects are :term:`alive`
|
||||
and which are :term:`dead`, and also to update references after
|
||||
objects have been moved.
|
||||
|
||||
Here's the scan method for the Scheme example::
|
||||
|
||||
static mps_res_t obj_scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit)
|
||||
{
|
||||
MPS_SCAN_BEGIN(ss) {
|
||||
while (base < limit) {
|
||||
obj_t obj = base;
|
||||
switch (obj->type.type) {
|
||||
case TYPE_PAIR:
|
||||
FIX(obj->pair.car);
|
||||
FIX(obj->pair.cdr);
|
||||
base = (char *)base + ALIGN(sizeof(pair_s));
|
||||
break;
|
||||
case TYPE_INTEGER:
|
||||
base = (char *)base + ALIGN(sizeof(integer_s));
|
||||
break;
|
||||
/* ... and so on for the other types ... */
|
||||
default:
|
||||
fprintf(stderr, "Unexpected object on the heap\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
} MPS_SCAN_END(ss);
|
||||
return MPS_RES_OK;
|
||||
}
|
||||
|
||||
The scan method receives a :term:`scan state` (``ss``) argument, and
|
||||
the block of memory to scan, from ``base`` (inclusive) to ``limit``
|
||||
(exclusive). This block of memory is known to be packed with objects
|
||||
belonging to the object format, and so the scan method loops over the
|
||||
objects in the block, dispatching on the type of each object, and then
|
||||
updating ``base`` to point to the next object in the block.
|
||||
|
||||
For each reference in an object the scan method fixes it by calling
|
||||
:c:func:`MPS_FIX12` via the macro ``FIX``, which is defined as
|
||||
follows::
|
||||
|
||||
#define FIX(ref) \
|
||||
do { \
|
||||
mps_addr_t _addr = (ref); /* copy to local to avoid type pun */ \
|
||||
mps_res_t res = MPS_FIX12(ss, &_addr); \
|
||||
if (res != MPS_RES_OK) return res; \
|
||||
(ref) = _addr; \
|
||||
} while(0)
|
||||
|
||||
Each call to :c:func:`MPS_FIX12` must appear between calls to the
|
||||
macros :c:func:`MPS_SCAN_BEGIN` and :c:func:`MPS_SCAN_END`. It's
|
||||
usually most convenient to call :c:func:`MPS_SCAN_BEGIN` at the start
|
||||
of the function and :c:func:`MPS_SCAN_END` at the end.
|
||||
|
||||
There are a few things to watch out for:
|
||||
|
||||
1. When the MPS calls your scan method, it may be part-way through
|
||||
moving your objects. It is therefore essential that the scan method
|
||||
only examine objects in the range of addresses it is given. Objects
|
||||
in other ranges of addresses are not guaranteed to be in a
|
||||
consistent state.
|
||||
|
||||
2. Scanning is an operation on the :term:`critical path` of the MPS,
|
||||
which means that it is important that it runs as quickly as
|
||||
possible.
|
||||
|
||||
3. The "fix" operation may update the reference. So if your language
|
||||
has :term:`tagged references <tagged reference>`, you must make
|
||||
sure that the tag is restored after the reference is updated.
|
||||
|
||||
4. The "fix" operation may fail by returning a :term:`result code`
|
||||
other than :c:macro:`MPS_RES_OK`. A scan function must propagate
|
||||
such a result code to the caller, and should do so as soon as
|
||||
practicable.
|
||||
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-format`, :ref:`topic-scanning`.
|
||||
|
||||
|
||||
---------------
|
||||
The skip method
|
||||
---------------
|
||||
|
||||
The :term:`skip method` is a function of type
|
||||
:c:type:`mps_fmt_skip_t`. It is called by the MPS to skip over an
|
||||
object belonging to the format.
|
||||
|
||||
Here's the skip method for the Scheme example::
|
||||
|
||||
static mps_addr_t obj_skip(mps_addr_t base)
|
||||
{
|
||||
obj_t obj = base;
|
||||
switch (obj->type.type) {
|
||||
case TYPE_PAIR:
|
||||
base = (char *)base + ALIGN(sizeof(pair_s));
|
||||
break;
|
||||
case TYPE_INTEGER:
|
||||
base = (char *)base + ALIGN(sizeof(integer_s));
|
||||
break;
|
||||
/* ... and so on for the other types ... */
|
||||
default:
|
||||
fprintf(stderr, "Unexpected object on the heap\n");
|
||||
abort();
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
The argument ``base`` is the address to the base of the object. The
|
||||
skip method must return the address of the base of the "next object":
|
||||
in formats of variant A like this one, this is the address of the word
|
||||
just past the end of the object.
|
||||
|
||||
You'll see that the code in the skip method that computes the "next
|
||||
object" is the same as the corresponding code in the :term:`scan
|
||||
method`, so that it's tempting for the latter to delegate this part of
|
||||
its functionality to the former.
|
||||
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-format`, :ref:`topic-scanning`.
|
||||
|
||||
|
||||
------------------
|
||||
The forward method
|
||||
------------------
|
||||
|
||||
The :term:`forward method` is a function of type
|
||||
:c:type:`mps_fmt_fwd_t`. It is called by the MPS after it has moved an
|
||||
object, and its task is to replace the old object with a
|
||||
:term:`forwarding object` pointing to the new location of the object.
|
||||
|
||||
The forwarding object must satisfy these properties:
|
||||
|
||||
1. It must be scannable and skippable, and so it will need to have a
|
||||
type field to distinguish it from other Scheme objects.
|
||||
|
||||
2. It must contain a pointer to the new location of the object (a
|
||||
:term:`forwarding pointer`).
|
||||
|
||||
3. The scan method and the skip method will both need to know the
|
||||
length of the forwarding object. This can be arbitarily long (in
|
||||
the case of string objects, for example) so it must contain a
|
||||
length field.
|
||||
|
||||
This poses a problem, because the above analysis suggests that
|
||||
forwarding objects need to contain at least three words, but Scheme
|
||||
objects might be as small as two words (for example, integers).
|
||||
|
||||
This conundrum can be solved by having two types of forwarding object.
|
||||
The first type is suitable for forwarding objects of three words or
|
||||
longer::
|
||||
|
||||
typedef struct fwd_s {
|
||||
type_t type; /* TYPE_FWD */
|
||||
obj_t fwd; /* forwarded object */
|
||||
size_t size; /* total size of this object */
|
||||
} fwd_s;
|
||||
|
||||
while the second type is suitable for forwarding objects of two words::
|
||||
|
||||
typedef struct fwd2_s {
|
||||
type_t type; /* TYPE_FWD2 */
|
||||
obj_t fwd; /* forwarded object */
|
||||
} fwd2_s;
|
||||
|
||||
Here's the forward method for the Scheme example::
|
||||
|
||||
static void obj_fwd(mps_addr_t old, mps_addr_t new)
|
||||
{
|
||||
obj_t obj = old;
|
||||
mps_addr_t limit = obj_skip(old);
|
||||
size_t size = (char *)limit - (char *)old;
|
||||
assert(size >= ALIGN(sizeof(fwd2_s)));
|
||||
if (size == ALIGN(sizeof(fwd2_s))) {
|
||||
obj->type.type = TYPE_FWD2;
|
||||
obj->fwd2.fwd = new;
|
||||
} else {
|
||||
obj->type.type = TYPE_FWD;
|
||||
obj->fwd.fwd = new;
|
||||
obj->fwd.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
The argument ``old`` is the old address of the object, and ``new`` is
|
||||
the location to which it has been moved.
|
||||
|
||||
The fowarding objects must be scannable and skippable, so the
|
||||
following code must be added to ``obj_scan`` and ``obj_skip``::
|
||||
|
||||
case TYPE_FWD:
|
||||
base = (char *)base + ALIGN(obj->fwd.size);
|
||||
break;
|
||||
case TYPE_FWD2:
|
||||
base = (char *)base + ALIGN(sizeof(fwd2_s));
|
||||
break;
|
||||
|
||||
.. note::
|
||||
|
||||
The Scheme interpreter has no objects consisting of a single word.
|
||||
If it did, the type and the forwarding pointer would have to be
|
||||
combined into a forwarding object consisting of a single word, for
|
||||
example by relying on the fact that pointers are never equal to
|
||||
the small integers that are used to represent types.
|
||||
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-format`.
|
||||
|
||||
|
||||
-----------------------
|
||||
The is-forwarded method
|
||||
-----------------------
|
||||
|
||||
The :term:`is-forwarded method` is a function of type
|
||||
:c:type:`mps_fmt_isfwd_t`. It is called by the MPS to determine if an
|
||||
object is a :term:`forwarding object`, and if it is, to determine the
|
||||
location where that object was moved.
|
||||
|
||||
Here's the is-forwarded method for the Scheme example::
|
||||
|
||||
static mps_addr_t obj_isfwd(mps_addr_t addr)
|
||||
{
|
||||
obj_t obj = addr;
|
||||
switch (obj->type.type) {
|
||||
case TYPE_FWD2:
|
||||
return obj->fwd2.fwd;
|
||||
case TYPE_FWD:
|
||||
return obj->fwd.fwd;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
It receives the address of an object, and returns the address to which
|
||||
that object was moved, or ``NULL`` if the object was not moved.
|
||||
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-format`.
|
||||
|
||||
|
||||
------------------
|
||||
The padding method
|
||||
------------------
|
||||
|
||||
The :term:`padding method` is a function of type
|
||||
:c:type:`mps_fmt_pad_t`. It is called by the MPS to fill a block of
|
||||
memory with a :term:`padding object`: this is an object that fills
|
||||
gaps in a block of :term:`formatted objects <formatted object>`, for
|
||||
example to enable the MPS to pack objects into fixed-size units (such
|
||||
as operating system :term:`pages <page>`).
|
||||
|
||||
A padding object must be scannable and skippable, and not confusable
|
||||
with a :term:`forwarding object`. This means they need a type and a
|
||||
size. However, padding objects might need to be as small as the
|
||||
alignment of the object format, which was specified to be a single
|
||||
word. As with forwarding objects, this can be solved by having two
|
||||
types of padding object. The first type is suitable for paddding
|
||||
objects of two words or longer::
|
||||
|
||||
typedef struct pad_s {
|
||||
type_t type; /* TYPE_PAD */
|
||||
size_t size; /* total size of this object */
|
||||
} pad_s;
|
||||
|
||||
while the second type is suitable for padding objects consisting of a
|
||||
single word::
|
||||
|
||||
typedef struct pad1_s {
|
||||
type_t type; /* TYPE_PAD1 */
|
||||
} pad1_s;
|
||||
|
||||
Here's the padding method::
|
||||
|
||||
static void obj_pad(mps_addr_t addr, size_t size)
|
||||
{
|
||||
obj_t obj = addr;
|
||||
assert(size >= ALIGN(sizeof(pad1_s)));
|
||||
if (size == ALIGN(sizeof(pad1_s))) {
|
||||
obj->type.type = TYPE_PAD1;
|
||||
} else {
|
||||
obj->type.type = TYPE_PAD;
|
||||
obj->pad.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
The argument ``addr`` is the address at which the padding object must be created, and ``size`` is its size in bytes: this will always be a multiple of the alignment of the object format.
|
||||
|
||||
The padding objects must be scannable and skippable, so the following
|
||||
code must be added to ``obj_scan`` and ``obj_skip``::
|
||||
|
||||
case TYPE_PAD:
|
||||
base = (char *)base + ALIGN(obj->pad.size);
|
||||
break;
|
||||
case TYPE_PAD1:
|
||||
base = (char *)base + ALIGN(sizeof(pad1_s));
|
||||
break;
|
||||
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-format`.
|
||||
|
||||
|
||||
-----------------
|
||||
Generation chains
|
||||
-----------------
|
||||
|
||||
The AMC pool requires not only an object format but a
|
||||
:term:`generation chain`. This specifies the generation structure of
|
||||
the :term:`generational garbage collection`.
|
||||
|
||||
The client program creates a generation chain by constructing an array
|
||||
of structures of type :c:type:`mps_gen_param_s` and passing them to
|
||||
:c:func:`mps_chain_create`. Here's the code for creating
|
||||
the generation chain for the Scheme interpreter::
|
||||
|
||||
mps_gen_param_s obj_gen_params[] = {
|
||||
{ 150, 0.85 },
|
||||
{ 170, 0.45 },
|
||||
};
|
||||
|
||||
res = mps_chain_create(&obj_chain,
|
||||
arena,
|
||||
LENGTH(obj_gen_params),
|
||||
obj_gen_params);
|
||||
if (res != MPS_RES_OK) error("Couldn't create obj chain");
|
||||
|
||||
|
||||
-----------------
|
||||
Creating the pool
|
||||
-----------------
|
||||
|
||||
Now you know enough to create an AMC pool! Let's review the pool
|
||||
creation code. First the object format::
|
||||
|
||||
struct mps_fmt_A_s obj_fmt_s = {
|
||||
sizeof(mps_word_t),
|
||||
obj_scan,
|
||||
obj_skip,
|
||||
NULL,
|
||||
obj_fwd,
|
||||
obj_isfwd,
|
||||
obj_pad,
|
||||
};
|
||||
|
||||
mps_fmt_t obj_fmt;
|
||||
res = mps_fmt_create_A(&obj_fmt, arena, &obj_fmt_s);
|
||||
if (res != MPS_RES_OK) error("Couldn't create obj format");
|
||||
|
||||
then the generation chain::
|
||||
|
||||
mps_gen_param_s obj_gen_params[] = {
|
||||
{ 150, 0.85 },
|
||||
{ 170, 0.45 },
|
||||
};
|
||||
|
||||
mps_chain_t obj_chain;
|
||||
res = mps_chain_create(&obj_chain,
|
||||
arena,
|
||||
LENGTH(obj_gen_params),
|
||||
obj_gen_params);
|
||||
if (res != MPS_RES_OK) error("Couldn't create obj chain");
|
||||
|
||||
and finally the pool::
|
||||
|
||||
mps_pool_t obj_pool;
|
||||
res = mps_pool_create(&obj_pool,
|
||||
arena,
|
||||
mps_class_amc(),
|
||||
obj_fmt,
|
||||
obj_chain);
|
||||
if (res != MPS_RES_OK) error("Couldn't create obj pool");
|
||||
|
||||
|
||||
-----
|
||||
Roots
|
||||
-----
|
||||
|
||||
|
||||
|
||||
--------------
|
||||
The trampoline
|
||||
--------------
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-root`.
|
||||
|
||||
|
||||
-------
|
||||
Threads
|
||||
-------
|
||||
|
||||
|
||||
|
||||
|
||||
----------
|
||||
Allocation
|
||||
----------
|
||||
|
||||
|
||||
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-allocation`.
|
||||
|
||||
|
||||
|
||||
--------
|
||||
Messages
|
||||
--------
|
||||
|
||||
|
||||
.. topics::
|
||||
|
||||
:ref:`topic-message`.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* (define (church n f a) (if (eqv? n 0) a (church (- n 1) f (f a))))
|
||||
* (church 1000 triangle 0)
|
||||
*
|
||||
* This won't produce interesting results but it will cause a garbage
|
||||
* This won't produce interesting results but it will cause garbage
|
||||
* collection cycles. Note that there's never any waiting for the MPS.
|
||||
* THAT'S THE POINT.
|
||||
*
|
||||
|
|
@ -109,9 +109,10 @@ enum {
|
|||
TYPE_PROMISE,
|
||||
TYPE_CHARACTER,
|
||||
TYPE_VECTOR,
|
||||
TYPE_FWD2, /* two-word broken heart */
|
||||
TYPE_FWD, /* three-words and up broken heart */
|
||||
TYPE_PAD1 /* one-word padding object */
|
||||
TYPE_FWD, /* three words and up forwarding object */
|
||||
TYPE_FWD2, /* two-word forwarding object */
|
||||
TYPE_PAD, /* two words and up padding object */
|
||||
TYPE_PAD1, /* one-word padding object */
|
||||
};
|
||||
|
||||
typedef struct type_s {
|
||||
|
|
@ -193,17 +194,22 @@ typedef struct vector_s {
|
|||
* See obj_pad, obj_fwd etc. to see how these are used.
|
||||
*/
|
||||
|
||||
typedef struct fwd2_s {
|
||||
type_t type; /* TYPE_FWD2 */
|
||||
obj_t fwd; /* forwarded object */
|
||||
} fwd2_s;
|
||||
|
||||
typedef struct fwd_s {
|
||||
type_t type; /* TYPE_FWD */
|
||||
obj_t fwd; /* forwarded object */
|
||||
size_t size; /* total size of this object */
|
||||
} fwd_s;
|
||||
|
||||
typedef struct fwd2_s {
|
||||
type_t type; /* TYPE_FWD2 */
|
||||
obj_t fwd; /* forwarded object */
|
||||
} fwd2_s;
|
||||
|
||||
typedef struct pad_s {
|
||||
type_t type; /* TYPE_PAD */
|
||||
size_t size; /* total size of this object */
|
||||
} pad_s;
|
||||
|
||||
typedef struct pad1_s {
|
||||
type_t type; /* TYPE_PAD1 */
|
||||
} pad1_s;
|
||||
|
|
@ -222,6 +228,7 @@ typedef union obj_u {
|
|||
vector_s vector;
|
||||
fwd2_s fwd2;
|
||||
fwd_s fwd;
|
||||
pad_s pad;
|
||||
} obj_s;
|
||||
|
||||
|
||||
|
|
@ -317,7 +324,7 @@ static obj_t obj_unquote_splic; /* "unquote-splicing" symbol */
|
|||
* be decoded by enclosing code.]
|
||||
*/
|
||||
|
||||
static jmp_buf *error_handler;
|
||||
static jmp_buf *error_handler = NULL;
|
||||
static char error_message[MSGMAX+1];
|
||||
|
||||
|
||||
|
|
@ -363,13 +370,17 @@ static void error(char *format, ...)
|
|||
{
|
||||
va_list args;
|
||||
|
||||
assert(error_handler != NULL);
|
||||
|
||||
va_start(args, format);
|
||||
vsprintf(error_message, format, args);
|
||||
vsnprintf(error_message, sizeof error_message, format, args);
|
||||
va_end(args);
|
||||
|
||||
longjmp(*error_handler, 1);
|
||||
if (error_handler) {
|
||||
longjmp(*error_handler, 1);
|
||||
} else {
|
||||
fprintf(stderr, "Fatal error during initialization: %s\n",
|
||||
error_message);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -826,8 +837,8 @@ static void print(obj_t obj, unsigned depth, FILE *stream)
|
|||
} break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
abort();
|
||||
assert(0);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2351,33 +2362,42 @@ static obj_t entry_gc(obj_t env, obj_t op_env, obj_t operator, obj_t operands)
|
|||
|
||||
/* special table */
|
||||
|
||||
static struct {char *name; obj_t *varp;} sptab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
obj_t *varp;
|
||||
} sptab[] = {
|
||||
{"()", &obj_empty},
|
||||
{"#[eof]", &obj_eof},
|
||||
{"#[error]", &obj_error},
|
||||
{"#t", &obj_true},
|
||||
{"#f", &obj_false},
|
||||
{"#[undefined]", &obj_undefined},
|
||||
{"#[tail]", &obj_tail}
|
||||
{"#[tail]", &obj_tail},
|
||||
};
|
||||
|
||||
|
||||
/* initial symbol table */
|
||||
|
||||
static struct {char *name; obj_t *varp;} isymtab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
obj_t *varp;
|
||||
} isymtab[] = {
|
||||
{"quote", &obj_quote},
|
||||
{"lambda", &obj_lambda},
|
||||
{"begin", &obj_begin},
|
||||
{"else", &obj_else},
|
||||
{"quasiquote", &obj_quasiquote},
|
||||
{"unquote", &obj_unquote},
|
||||
{"unquote-splicing", &obj_unquote_splic}
|
||||
{"unquote-splicing", &obj_unquote_splic},
|
||||
};
|
||||
|
||||
|
||||
/* operator table */
|
||||
|
||||
static struct {char *name; entry_t entry;} optab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
entry_t entry;
|
||||
} optab[] = {
|
||||
{"quote", entry_quote},
|
||||
{"define", entry_define},
|
||||
{"set!", entry_set},
|
||||
|
|
@ -2392,13 +2412,16 @@ static struct {char *name; entry_t entry;} optab[] = {
|
|||
{"letrec", entry_letrec},
|
||||
{"do", entry_do},
|
||||
{"delay", entry_delay},
|
||||
{"quasiquote", entry_quasiquote}
|
||||
{"quasiquote", entry_quasiquote},
|
||||
};
|
||||
|
||||
|
||||
/* function table */
|
||||
|
||||
static struct {char *name; entry_t entry;} funtab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
entry_t entry;
|
||||
} funtab[] = {
|
||||
{"not", entry_not},
|
||||
{"boolean?", entry_booleanp},
|
||||
{"eqv?", entry_eqvp},
|
||||
|
|
@ -2442,7 +2465,7 @@ static struct {char *name; entry_t entry;} funtab[] = {
|
|||
{"eval", entry_eval},
|
||||
{"symbol->string", entry_symbol_to_string},
|
||||
{"string->symbol", entry_string_to_symbol},
|
||||
{"gc", entry_gc}
|
||||
{"gc", entry_gc},
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -2475,12 +2498,12 @@ static struct {char *name; entry_t entry;} funtab[] = {
|
|||
|
||||
static mps_res_t obj_scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit)
|
||||
{
|
||||
#define FIX(ref) \
|
||||
do { \
|
||||
#define FIX(ref) \
|
||||
do { \
|
||||
mps_addr_t _addr = (ref); /* copy to local to avoid type pun */ \
|
||||
mps_res_t res = MPS_FIX12(ss, &_addr); \
|
||||
if (res != MPS_RES_OK) return res; \
|
||||
(ref) = _addr; \
|
||||
mps_res_t res = MPS_FIX12(ss, &_addr); \
|
||||
if (res != MPS_RES_OK) return res; \
|
||||
(ref) = _addr; \
|
||||
} while(0)
|
||||
|
||||
MPS_SCAN_BEGIN(ss) {
|
||||
|
|
@ -2530,20 +2553,21 @@ static mps_res_t obj_scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit)
|
|||
ALIGN(offsetof(vector_s, vector) +
|
||||
obj->vector.length * sizeof(obj->vector.vector[0]));
|
||||
break;
|
||||
case TYPE_FWD:
|
||||
base = (char *)base + ALIGN(obj->fwd.size);
|
||||
break;
|
||||
case TYPE_FWD2:
|
||||
base = (char *)base + ALIGN(sizeof(fwd2_s));
|
||||
break;
|
||||
case TYPE_FWD:
|
||||
base = (char *)base + ALIGN(obj->fwd.size);
|
||||
case TYPE_PAD:
|
||||
base = (char *)base + ALIGN(obj->pad.size);
|
||||
break;
|
||||
case TYPE_PAD1:
|
||||
base = (char *)base + ALIGN(sizeof(pad1_s));
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
fprintf(stderr, "Unexpected object on the heap\n");
|
||||
abort();
|
||||
return MPS_RES_FAIL;
|
||||
}
|
||||
}
|
||||
} MPS_SCAN_END(ss);
|
||||
|
|
@ -2594,20 +2618,21 @@ static mps_addr_t obj_skip(mps_addr_t base)
|
|||
ALIGN(offsetof(vector_s, vector) +
|
||||
obj->vector.length * sizeof(obj->vector.vector[0]));
|
||||
break;
|
||||
case TYPE_FWD:
|
||||
base = (char *)base + ALIGN(obj->fwd.size);
|
||||
break;
|
||||
case TYPE_FWD2:
|
||||
base = (char *)base + ALIGN(sizeof(fwd2_s));
|
||||
break;
|
||||
case TYPE_FWD:
|
||||
base = (char *)base + ALIGN(obj->fwd.size);
|
||||
case TYPE_PAD:
|
||||
base = (char *)base + ALIGN(obj->pad.size);
|
||||
break;
|
||||
case TYPE_PAD1:
|
||||
base = (char *)base + ALIGN(sizeof(pad1_s));
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
fprintf(stderr, "Unexpected object on the heap\n");
|
||||
abort();
|
||||
return NULL;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
|
@ -2625,10 +2650,10 @@ static mps_addr_t obj_isfwd(mps_addr_t addr)
|
|||
{
|
||||
obj_t obj = addr;
|
||||
switch (obj->type.type) {
|
||||
case TYPE_FWD2:
|
||||
return obj->fwd2.fwd;
|
||||
case TYPE_FWD:
|
||||
return obj->fwd.fwd;
|
||||
case TYPE_FWD2:
|
||||
return obj->fwd2.fwd;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -2663,10 +2688,10 @@ static void obj_fwd(mps_addr_t old, mps_addr_t new)
|
|||
*
|
||||
* The job of `obj_pad` is to fill in a block of memory with a padding
|
||||
* object that will be skipped by `obj_scan` or `obj_skip` but does
|
||||
* nothing else. Because we've chosen to align to single words, we may
|
||||
* nothing else. Because we've chosen to align to single words, we may
|
||||
* have to pad a single word, so we have a special single-word padding
|
||||
* object, `PAD1` for that purpose. Otherwise we can use forwarding
|
||||
* objects with their `fwd` fields set to `NULL`.
|
||||
* object, `PAD1` for that purpose. Otherwise we can use multi-word
|
||||
* padding objects, `PAD`.
|
||||
*/
|
||||
|
||||
static void obj_pad(mps_addr_t addr, size_t size)
|
||||
|
|
@ -2675,31 +2700,13 @@ static void obj_pad(mps_addr_t addr, size_t size)
|
|||
assert(size >= ALIGN(sizeof(pad1_s)));
|
||||
if (size == ALIGN(sizeof(pad1_s))) {
|
||||
obj->type.type = TYPE_PAD1;
|
||||
} else if (size == ALIGN(sizeof(fwd2_s))) {
|
||||
obj->type.type = TYPE_FWD2;
|
||||
obj->fwd2.fwd = NULL;
|
||||
} else {
|
||||
obj->type.type = TYPE_FWD;
|
||||
obj->fwd.fwd = NULL;
|
||||
obj->fwd.size = size;
|
||||
obj->type.type = TYPE_PAD;
|
||||
obj->pad.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* obj_copy -- object format copy method %%MPS
|
||||
*
|
||||
* The job of `obj_copy` is to make a copy of an object.
|
||||
* TODO: Explain why this exists.
|
||||
*/
|
||||
|
||||
static void obj_copy(mps_addr_t old, mps_addr_t new)
|
||||
{
|
||||
mps_addr_t limit = obj_skip(old);
|
||||
size_t size = (char *)limit - (char *)old;
|
||||
(void)memcpy(new, old, size);
|
||||
}
|
||||
|
||||
|
||||
/* obj_fmt_s -- object format parameter structure %%MPS
|
||||
*
|
||||
* This is simply a gathering of the object format methods and the chosen
|
||||
|
|
@ -2710,10 +2717,10 @@ struct mps_fmt_A_s obj_fmt_s = {
|
|||
sizeof(mps_word_t),
|
||||
obj_scan,
|
||||
obj_skip,
|
||||
obj_copy,
|
||||
NULL,
|
||||
obj_fwd,
|
||||
obj_isfwd,
|
||||
obj_pad
|
||||
obj_pad,
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -2908,7 +2915,7 @@ static void *start(void *p, size_t s)
|
|||
|
||||
static mps_gen_param_s obj_gen_params[] = {
|
||||
{ 150, 0.85 },
|
||||
{ 170, 0.45 }
|
||||
{ 170, 0.45 },
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ static obj_t obj_unquote_splic; /* "unquote-splicing" symbol */
|
|||
* be decoded by enclosing code.]
|
||||
*/
|
||||
|
||||
static jmp_buf *error_handler;
|
||||
static jmp_buf *error_handler = NULL;
|
||||
static char error_message[MSGMAX+1];
|
||||
|
||||
|
||||
|
|
@ -247,13 +247,17 @@ static void error(char *format, ...)
|
|||
{
|
||||
va_list args;
|
||||
|
||||
assert(error_handler != NULL);
|
||||
|
||||
va_start(args, format);
|
||||
vsprintf(error_message, format, args);
|
||||
vsnprintf(error_message, sizeof error_message, format, args);
|
||||
va_end(args);
|
||||
|
||||
longjmp(*error_handler, 1);
|
||||
if (error_handler) {
|
||||
longjmp(*error_handler, 1);
|
||||
} else {
|
||||
fprintf(stderr, "Fatal error during initialization: %s\n",
|
||||
error_message);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -625,8 +629,8 @@ static void print(obj_t obj, unsigned depth, FILE *stream)
|
|||
} break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
abort();
|
||||
assert(0);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2059,32 +2063,41 @@ static obj_t entry_string_to_symbol(obj_t env, obj_t op_env, obj_t operator, obj
|
|||
|
||||
/* special table */
|
||||
|
||||
static struct {char *name; obj_t *varp;} sptab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
obj_t *varp;
|
||||
} sptab[] = {
|
||||
{"()", &obj_empty},
|
||||
{"#[eof]", &obj_eof},
|
||||
{"#[error]", &obj_error},
|
||||
{"#t", &obj_true},
|
||||
{"#f", &obj_false},
|
||||
{"#[undefined]", &obj_undefined}
|
||||
{"#[undefined]", &obj_undefined},
|
||||
};
|
||||
|
||||
|
||||
/* initial symbol table */
|
||||
|
||||
static struct {char *name; obj_t *varp;} isymtab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
obj_t *varp;
|
||||
} isymtab[] = {
|
||||
{"quote", &obj_quote},
|
||||
{"lambda", &obj_lambda},
|
||||
{"begin", &obj_begin},
|
||||
{"else", &obj_else},
|
||||
{"quasiquote", &obj_quasiquote},
|
||||
{"unquote", &obj_unquote},
|
||||
{"unquote-splicing", &obj_unquote_splic}
|
||||
{"unquote-splicing", &obj_unquote_splic},
|
||||
};
|
||||
|
||||
|
||||
/* operator table */
|
||||
|
||||
static struct {char *name; entry_t entry;} optab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
entry_t entry;
|
||||
} optab[] = {
|
||||
{"quote", entry_quote},
|
||||
{"define", entry_define},
|
||||
{"set!", entry_set},
|
||||
|
|
@ -2099,13 +2112,16 @@ static struct {char *name; entry_t entry;} optab[] = {
|
|||
{"letrec", entry_letrec},
|
||||
{"do", entry_do},
|
||||
{"delay", entry_delay},
|
||||
{"quasiquote", entry_quasiquote}
|
||||
{"quasiquote", entry_quasiquote},
|
||||
};
|
||||
|
||||
|
||||
/* function table */
|
||||
|
||||
static struct {char *name; entry_t entry;} funtab[] = {
|
||||
static struct {
|
||||
char *name;
|
||||
entry_t entry;
|
||||
} funtab[] = {
|
||||
{"not", entry_not},
|
||||
{"boolean?", entry_booleanp},
|
||||
{"eqv?", entry_eqvp},
|
||||
|
|
@ -2146,7 +2162,7 @@ static struct {char *name; entry_t entry;} funtab[] = {
|
|||
{"vector-fill!", entry_vector_fill},
|
||||
{"eval", entry_eval},
|
||||
{"symbol->string", entry_symbol_to_string},
|
||||
{"string->symbol", entry_string_to_symbol}
|
||||
{"string->symbol", entry_string_to_symbol},
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
TODO
|
||||
|
||||
2. Create new types of objects for pools and topics.
|
||||
|
||||
3. Glossary entries need permalinks. See
|
||||
<https://bitbucket.org/birkenfeld/sphinx/issue/996/expose-glossary-entry-link-on-hover>
|
||||
|
||||
5. Fix the general index so that (1) aren't interpreted as subentries.
|
||||
|
||||
7. Re-do the diagrams in vector form and using the colour palette.
|
||||
|
||||
8. Use a better bibliography extension. See for example
|
||||
<http://wnielson.bitbucket.org/projects/sphinx-natbib/>
|
||||
|
||||
9. Hyphenate long function names across line endings (but what if you
|
||||
copy them?)
|
||||
2. Create new types of objects for pools and topics.
|
||||
|
||||
3. Glossary entries need permalinks. See
|
||||
<https://bitbucket.org/birkenfeld/sphinx/issue/996/expose-glossary-entry-link-on-hover>
|
||||
|
||||
5. Fix the general index so that (1) aren't interpreted as
|
||||
subentries.
|
||||
|
||||
7. Re-do the diagrams in vector form and using the colour palette.
|
||||
|
||||
8. Use a better bibliography extension. See for example
|
||||
<http://wnielson.bitbucket.org/projects/sphinx-natbib/>
|
||||
|
||||
9. Hyphenate long function names across line endings (but what if you
|
||||
copy them?)
|
||||
|
||||
11. Support MMREF-style anchors to the glossary (#garbage.collection
|
||||
instead of #garbage-collection).
|
||||
|
|
@ -21,17 +22,88 @@ TODO
|
|||
|
||||
QUERIES
|
||||
|
||||
1. Does the object format description (struct mps_fmt_A_s) have to be
|
||||
static?
|
||||
1. Does the object format description (struct mps_fmt_A_s etc) have
|
||||
to be static? Is it OK to throw it away after calling
|
||||
mps_fmt_create?
|
||||
|
||||
2. What is the difference, if any, between mps_word_t and MPS_T_WORD?
|
||||
Same question for the generation structure (mps_gen_param_s)?
|
||||
|
||||
2. What is the difference, if any, between mps_word_t and MPS_T_WORD?
|
||||
|
||||
3. How can I explain why the Scheme example uses sizeof(mps_word_t)
|
||||
as its alignment? Why not MPS_PF_ALIGN (or are client programs not
|
||||
supposed to look at mpstd.h)? Why not something of its own
|
||||
manufacture, like sizeof(union {long, size_t, void*})?
|
||||
|
||||
4. Why does the Scheme example have a copy method in its object
|
||||
format when the reference manual says it's obsolete?
|
||||
|
||||
3. How can I explain why the Scheme example uses sizeof(mps_word_t) as
|
||||
its alignment? Why not MPS_PF_ALIGN? Why not something of its own
|
||||
manufacture, like sizeof(union {long, size_t, void*})?
|
||||
I removed it.
|
||||
|
||||
5. What is the difference between the "event stream" and the
|
||||
"telemetry stream"? Are these names for the same thing? Or is
|
||||
there a distinction (for example, "event stream" refers to the
|
||||
internal, unfiltered, stream of events and "telemetry stream"
|
||||
refers to the filtered stream)?
|
||||
|
||||
6. The location dependency functions all take an arena as an
|
||||
argument. What is the role of this argument?
|
||||
|
||||
7. What is the role of the third (addr) argument to mps_ld_isstale?
|
||||
LDIsStale says "UNUSED(addr);" so maybe it is unused.
|
||||
|
||||
8. Is the material in the pool class comparison table at all accurate?
|
||||
|
||||
9. This code seems a bit confused about what to do:
|
||||
|
||||
4. Why does the Scheme example have a copy method in its object format
|
||||
when the reference manual says it's obsolete? Can I remove it?
|
||||
assert(0);
|
||||
fprintf(stderr, "Unexpected object on the heap\n");
|
||||
abort();
|
||||
return MPS_RES_FAIL;
|
||||
|
||||
I took out the assertion and the return.
|
||||
|
||||
10. How does fixing interact with tagged references? Do I need to
|
||||
remove the tag before fixing a reference? Do I need to restore the
|
||||
tag afterwards? I thought that both would be necessary but the
|
||||
critical path documentation has an example from OpenDylan with
|
||||
tagged references that does neither:
|
||||
<//info.ravenbrook.com/project/mps/master/design/critical-path.txt>
|
||||
|
||||
11. This code from mps_chat in the Scheme example is wrong:
|
||||
|
||||
if (type == mps_message_type_gc_start()) {
|
||||
printf("Collection %lu started.\n", (unsigned long)mps_collections(arena));
|
||||
|
||||
mps_collections returns the total number of collections to date,
|
||||
not the number of the collection that posted the message. This
|
||||
means that if there have been multiple collections since the last
|
||||
time the message queue was emptied, the output will look like
|
||||
|
||||
Collection 47 started.
|
||||
...
|
||||
Collection 47 started.
|
||||
...
|
||||
Collection 47 started.
|
||||
...
|
||||
Collection 47 started.
|
||||
|
||||
12. It seems "tricky" to re-use fowarding objects as padding objects
|
||||
by setting their forwarding pointer to NULL. It would be simpler
|
||||
to explain if we had TYPE_PAD for multiple-word padding objects.
|
||||
There's enough to explain as it is!
|
||||
|
||||
I made this change.
|
||||
|
||||
13. The Scheme example says, "Adapting it to use the MPS took
|
||||
approximately two hours". I doubt this would be the common case,
|
||||
and it would be better to under-promise and over-deliver.
|
||||
|
||||
14. I need to document the values in mps_gen_param_s. I believe they
|
||||
are the capacity (size of the generation in kilobytes) and the
|
||||
mortality (the proportion of objects in this generation that are
|
||||
expected to die in a collection). But what do they mean to the
|
||||
MPS? And how should one go about choosing values?
|
||||
|
||||
|
||||
DONE
|
||||
|
|
|
|||
|
|
@ -1173,8 +1173,8 @@ Declared in ``mps.h``
|
|||
browsers), or a null pointer if this is not possible.
|
||||
|
||||
It is recommended that a null pointer be returned for
|
||||
:term:`padding objects <padding object>` and :term:`forwarded
|
||||
objects <forwarded object>`.
|
||||
:term:`padding objects <padding object>` and :term:`forwarding
|
||||
objects <forwarding object>`.
|
||||
|
||||
The exact meaning of the return value is up to the :term:`client
|
||||
program`, but it would typically bear some relation to a class or
|
||||
|
|
@ -1295,7 +1295,7 @@ Declared in ``mps.h``
|
|||
|
||||
*addr* is the address of a candidate object.
|
||||
|
||||
If the *addr* is the address of a :term:`forwarded object`, return
|
||||
If the *addr* is the address of a :term:`forwarding object`, return
|
||||
the address where the object was moved to. This must be the value
|
||||
of the *new* argument supplied to the :term:`forward method` when
|
||||
the object was moved. If not, return a null pointer.
|
||||
|
|
@ -1362,10 +1362,10 @@ Declared in ``mps.h``
|
|||
:c:macro:`MPS_RES_OK`.
|
||||
|
||||
The scan method for an object format is called when the MPS needs
|
||||
to scan objects in a block area of memory containing objects
|
||||
belonging to that format. The scan method is called with a scan
|
||||
state and the base and limit of the block of objects to scan. It
|
||||
must then indicate references within the objects by calling
|
||||
to scan objects in a block of memory containing objects belonging
|
||||
to that format. The scan method is called with a scan state and
|
||||
the base and limit of the block of objects to scan. It must then
|
||||
indicate references within the objects by calling
|
||||
:c:func:`MPS_FIX1` and :c:func:`MPS_FIX2`.
|
||||
|
||||
.. topics::
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue