From d1bc2a6855cb52c46bc620c3edbf08d83c697c8e Mon Sep 17 00:00:00 2001 From: Gareth Rees Date: Sat, 3 Nov 2012 17:38:57 +0000 Subject: [PATCH] Write "advanced topics" chapter of the user guide. Copied from Perforce Change: 180303 ServerID: perforce.ravenbrook.com --- mps/example/scheme/test-common.scm | 13 +- mps/manual/source/diagrams/symbol-table.svg | 448 ++++++++++++++++++++ mps/manual/source/guide/debug.rst | 8 +- mps/manual/source/guide/index.rst | 2 + mps/manual/source/guide/lang.rst | 20 +- mps/manual/source/guide/overview.rst | 8 +- mps/manual/source/pool/awl.rst | 9 +- mps/manual/source/topic/finalization.rst | 105 +---- mps/manual/source/topic/format.rst | 2 + mps/manual/source/topic/location.rst | 200 +-------- 10 files changed, 507 insertions(+), 308 deletions(-) create mode 100644 mps/manual/source/diagrams/symbol-table.svg diff --git a/mps/example/scheme/test-common.scm b/mps/example/scheme/test-common.scm index de4051b8eb5..e9d1c8bdc8b 100644 --- a/mps/example/scheme/test-common.scm +++ b/mps/example/scheme/test-common.scm @@ -1,12 +1,13 @@ ;;; test-common.scm -- common definitions for the Scheme tests (define (check exp result) - (write-string "test: ") (write exp) (newline) - (write-string "expect: ") (write result) (newline) - (define actually (eval exp)) - (write-string "got: ") (write actually) (newline) - (if (not (equal? actually result)) - (error "failed!"))) + (let ((actually (eval exp))) + (if (not (equal? actually result)) + (begin + (write-string "test: ") (write exp) (newline) + (write-string "expect: ") (write result) (newline) + (write-string "got: ") (write actually) (newline) + (error "failed!"))))) ;; Return (f (f (f ... (f a) ... ))) with n invocations of f. (define (church n f a) diff --git a/mps/manual/source/diagrams/symbol-table.svg b/mps/manual/source/diagrams/symbol-table.svg new file mode 100644 index 00000000000..44ee9f6094c --- /dev/null +++ b/mps/manual/source/diagrams/symbol-table.svg @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + TABLE + + keys + + values + + STRING + + "name" + + SYMBOL + + name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + keys + values + + diff --git a/mps/manual/source/guide/debug.rst b/mps/manual/source/guide/debug.rst index dec1801f598..2c10c5ff640 100644 --- a/mps/manual/source/guide/debug.rst +++ b/mps/manual/source/guide/debug.rst @@ -170,11 +170,11 @@ implements the environment as a list of *frames*, each of which is a list of *bindings*, each binding being a pair of a symbol and its value, as shown here: - .. figure:: ../diagrams/scheme-env.svg - :align: center - :alt: Diagram: The environment data structure in the Scheme interpreter. +.. figure:: ../diagrams/scheme-env.svg + :align: center + :alt: Diagram: The environment data structure in the Scheme interpreter. - The environment data structure in the Scheme interpreter. + The environment data structure in the Scheme interpreter. In this case, because the evaluation is taking place at top level, there is only one frame in the environment (the global frame). And diff --git a/mps/manual/source/guide/index.rst b/mps/manual/source/guide/index.rst index 2906352babe..abd9ef242ca 100644 --- a/mps/manual/source/guide/index.rst +++ b/mps/manual/source/guide/index.rst @@ -11,3 +11,5 @@ Guide lang debug perf + advanced + diff --git a/mps/manual/source/guide/lang.rst b/mps/manual/source/guide/lang.rst index b0df22a9f9a..8490df11136 100644 --- a/mps/manual/source/guide/lang.rst +++ b/mps/manual/source/guide/lang.rst @@ -32,14 +32,14 @@ I'll be quoting the relevant sections of code as needed, but you may find it helpful to experiment with this interpeter yourself, in either of its versions: - :download:`scheme-malloc.c <../../../example/scheme/scheme-malloc.c>` +:download:`scheme-malloc.c <../../../example/scheme/scheme-malloc.c>` - The Scheme interpreter before integration with the MPS, using - :term:`malloc` and :term:`free (2)` for memory management. + The Scheme interpreter before integration with the MPS, using + :term:`malloc` and :term:`free (2)` for memory management. - :download:`scheme.c <../../../example/scheme/scheme.c>` +:download:`scheme.c <../../../example/scheme/scheme.c>` - The Scheme interpreter after integration with the MPS. + The Scheme interpreter after integration with the MPS. This simple interpreter allocates two kinds of objects on the :term:`heap`: @@ -476,11 +476,11 @@ The :term:`forward method` is a function of type object, and its task is to replace the old object with a :term:`forwarding object` pointing to the new location of the object. - .. figure:: ../diagrams/copying.svg - :align: center - :alt: Diagram: Copying garbage collection. +.. figure:: ../diagrams/copying.svg + :align: center + :alt: Diagram: Copying garbage collection. - Copying garbage collection. + Copying garbage collection. The forwarding object must satisfy these properties: @@ -785,6 +785,8 @@ And finally the :term:`pool`:: single: root; creating single: Scheme; root +.. _guide-lang-root: + Roots ----- diff --git a/mps/manual/source/guide/overview.rst b/mps/manual/source/guide/overview.rst index ec9f7c7fd37..8b9789d2289 100644 --- a/mps/manual/source/guide/overview.rst +++ b/mps/manual/source/guide/overview.rst @@ -75,11 +75,11 @@ Technical introduction The figure below gives a simplified picture of a program's memory from the point of view of the Memory Pool System. - .. figure:: ../diagrams/overview.svg - :align: center - :alt: Diagram: Overview of the Memory Pool System. +.. figure:: ../diagrams/overview.svg + :align: center + :alt: Diagram: Overview of the Memory Pool System. - Overview of the Memory Pool System. + Overview of the Memory Pool System. The **arena** is the top-level data structure in the MPS. An :term:`arena` is responsible for requesting :term:`memory (3)` from diff --git a/mps/manual/source/pool/awl.rst b/mps/manual/source/pool/awl.rst index ad1a367198b..e9007710e0d 100644 --- a/mps/manual/source/pool/awl.rst +++ b/mps/manual/source/pool/awl.rst @@ -16,8 +16,8 @@ AWL (Automatic Weak Linked) =========================== **AWL** is an :term:`automatically managed ` :term:`pool class` that may contain :term:`weak -references (1)`. +management>` :term:`non-moving ` +:term:`pool class` that may contain :term:`weak references (1)`. The purpose of this pool class is to allow the client to implement :term:`weak-key `, :term:`weak-value ` (and prompt deletion of keys in a :term:`weak-value hash table`), an AWL pool -allows each object to have a :dfn:`dependent object`. +allows each object to have a :dfn:`dependent object`. (This is where +the "Linked" in the name of the pool class comes from.) The dependent object is specified by the ``find_dependent`` argument to :c:func:`mps_pool_create` when creating an AWL pool. This is a @@ -355,4 +356,4 @@ AWL interface The dependent object need not be in memory managed by the MPS, but if it is, then it must be in a :term:`non-moving ` pool in the same arena as ``addr``. + garbage collector>` pool in the same arena as ``addr``. diff --git a/mps/manual/source/topic/finalization.rst b/mps/manual/source/topic/finalization.rst index 601f98ccedb..2ce62c9e397 100644 --- a/mps/manual/source/topic/finalization.rst +++ b/mps/manual/source/topic/finalization.rst @@ -107,108 +107,11 @@ program that registers the same block multiple times must cope with either behaviour. -.. index:: - single: finalization; example - single: Scheme; finalization - single: Scheme; ports - -Example: ports in Scheme ------------------------- - -In Scheme, an open file is represented by a *port*. In the toy Scheme -example, a port is a wrapper around a Standard C file handle:: - - typedef struct port_s { - type_t type; /* TYPE_PORT */ - obj_t name; /* name of stream */ - FILE *stream; - } port_s; - -The Scheme procedure ``open-input-file`` takes a filename and returns -a port: - -.. code-block:: c - :emphasize-lines: 21 - - /* (open-input-file filename) - * Opens filename for input, with empty file options, and returns the - * obtained port. - * See R4RS 6.10.1 - */ - static obj_t entry_open_input_file(obj_t env, obj_t op_env, obj_t operator, obj_t operands) - { - obj_t filename; - FILE *stream; - obj_t port; - mps_addr_t port_ref; - eval_args(operator->operator.name, env, op_env, operands, 1, &filename); - unless(TYPE(filename) == TYPE_STRING) - error("%s: argument must be a string", operator->operator.name); - stream = fopen(filename->string.string, "r"); - if(stream == NULL) - error("%s: cannot open input file", operator->operator.name); - port = make_port(filename, stream); - - port_ref = port; - mps_finalize(arena, &port_ref); - - return port; - } - -Each time around the read–eval–print loop, the interpreter polls the -message queue for finalization messages, and when it finds one it -closes the port's underlying file handle: - -.. code-block:: c - :emphasize-lines: 9, 12 - - mps_message_type_t type; - - while (mps_message_queue_type(&type, arena)) { - mps_message_t message; - mps_bool_t b; - b = mps_message_get(&message, arena, type); - assert(b); /* we just checked there was one */ - - if (type == mps_message_type_finalization()) { - mps_addr_t port_ref; - obj_t port; - mps_message_finalization_ref(&port_ref, arena, message); - port = port_ref; - assert(TYPE(port) == TYPE_PORT); - printf("Port to file \"%s\" is dying. Closing file.\n", - port->port.name->string.string); - (void)fclose(port->port.stream); - } else { - /* ... handle other message types ... */ - } - - mps_message_discard(arena, message); - } - -Here's an example session showing finalization taking place: - -.. code-block:: none - :emphasize-lines: 8 - - MPS Toy Scheme Example - 9960, 0> (open-input-file "scheme.c") - #[port "scheme.c"] - 10064, 0> (gc) - Collection started. - Why: Client requests: immediate full collection. - Clock: 3401 - Port to file "scheme.c" is dying. Closing file. - Collection finished. - live 10040 - condemned 10088 - not_condemned 0 - clock: 3807 - - .. index:: pair: finalization; cautions +.. _topic-finalization-cautions: + Cautions -------- @@ -233,7 +136,9 @@ Cautions finalization messages are only processed at the end of the read–eval–print loop, so a program that opens many files may run out of handles even though the associated objects are all - finalizable, as shown here:: + finalizable, as shown here: + + .. code-block:: none MPS Toy Scheme Example 9960, 0> (define (repeat n f _) (if (eqv? n 0) '() (repeat (- n 1) f (f)))) diff --git a/mps/manual/source/topic/format.rst b/mps/manual/source/topic/format.rst index 796810ca1e8..2ac63b70aa5 100644 --- a/mps/manual/source/topic/format.rst +++ b/mps/manual/source/topic/format.rst @@ -291,6 +291,8 @@ For example:: .. index:: pair: object format; cautions +.. _topic-format-cautions: + Cautions -------- diff --git a/mps/manual/source/topic/location.rst b/mps/manual/source/topic/location.rst index d001470c180..160dc73e7aa 100644 --- a/mps/manual/source/topic/location.rst +++ b/mps/manual/source/topic/location.rst @@ -20,6 +20,8 @@ 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 @@ -45,49 +47,6 @@ if any of the blocks whose location has been depended on might have moved since the respective dependency was made. -.. index:: - single: location dependency; example - single: hash table; address-based example - single: Scheme; address-based hash table - -Example: ``eq?`` hash table ---------------------------- - -The toy Scheme interpreter contains a simple address-based (``eq?``) -hash table implementation. It hashes the addresses of its keys, and so -depends on their location. - -If it fails to take account of this location dependency, the hash -tables become invalid after a garbage collection. In the interaction -shown below (with a naïve version of the code) you'll see that -although the keys remain present in the table after garbage -collection, they cannot be found. This is because their locations (and -hence their hashes) have changed, but their positions in the table -have not been updated to match. - -.. code-block:: none - - MPS Toy Scheme Example - 10240, 0> (define ht (make-eq-hashtable)) - ht - 10584, 0> (hashtable-set! ht 'one 1) - 10768, 0> (hashtable-set! ht 'two 2) - 10952, 0> (hashtable-set! ht 'three 3) - 11136, 0> ht - #[hashtable (two 2) (three 3) (one 1)] - 11136, 0> (hashtable-ref ht 'two #f) - 2 - 11280, 0> (gc) - 11304, 1> (hashtable-ref ht 'one #f) - #f - 11448, 1> (hashtable-ref ht 'two #f) - #f - 11592, 1> (hashtable-ref ht 'three #f) - #f - 11736, 1> ht - #[hashtable (two 2) (three 3) (one 1)] - - .. index:: single: location dependency; creating @@ -99,49 +58,28 @@ The :term:`client program` must provide space for the larger structure. This structure can be in memory managed by the MPS or elsewhere; that doesn't matter. -For example, the Scheme interpreter inlines the location dependency in -its hash table structure: +For example, the toy Scheme interpreter inlines the location +dependency in its hash table structure: .. code-block:: c - :emphasize-lines: 3 + :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 -function :c:func:`mps_ld_reset`. +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. -For example: - -.. code-block:: c - :emphasize-lines: 15 - - static obj_t make_table(void) - { - obj_t obj; - mps_addr_t addr; - size_t size = ALIGN(sizeof(table_s)); - do { - mps_res_t res = mps_reserve(&addr, obj_ap, size); - if (res != MPS_RES_OK) error("out of memory in make_table"); - obj = addr; - obj->table.type = TYPE_TABLE; - obj->table.buckets = NULL; - } while (!mps_commit(obj_ap, addr, size)); - total += size; - obj->table.buckets = make_buckets(8); - mps_ld_reset(&obj->table.ld, arena); - return obj; - } - 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. @@ -164,43 +102,20 @@ 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, addresses are hashed -during the call to the function ``buckets_find``, so the key must be -added to the location dependency before that: +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 int table_try_set(obj_t tbl, obj_t key, obj_t value) + static unsigned long eq_hash(obj_t obj, mps_ld_t ld) { - struct bucket_s *b; - mps_ld_add(&tbl->table.ld, arena, key); - b = buckets_find(tbl->table.buckets, key); - if (b == NULL) - return 0; - if (b->key == NULL) - b->key = key; - b->value = value; - return 1; + 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)); } - static void table_set(obj_t tbl, obj_t key, obj_t value) - { - if (!table_try_set(tbl, key, value)) { - int res; - table_rehash(tbl, tbl->table.buckets->buckets.length * 2, NULL); - res = table_try_set(tbl, key, value); - assert(res); /* rehash should have made room */ - } - } - -.. note:: - - The garbage collector may run at any time during this operation, - so the table may already be stale while the new key and value are - being added. We postpone worrying about this until the next - lookup, when the staleness will be discovered. - .. index:: single: location dependency; testing staleness @@ -244,46 +159,15 @@ point you need to: 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: +the new locations of the blocks. .. code-block:: c - :emphasize-lines: 13, 19, 37 - - /* Rehash 'tbl' so that it has 'new_length' buckets. If 'key' is found - * during this process, return the bucket containing 'key', otherwise - * return NULL. - */ - static struct bucket_s *table_rehash(obj_t tbl, size_t new_length, obj_t key) - { - size_t i; - obj_t new_buckets; - struct bucket_s *key_bucket = NULL; - - assert(tbl->type.type == TYPE_TABLE); - new_buckets = make_buckets(new_length); - mps_ld_reset(&tbl->table.ld, arena); - - for (i = 0; i < tbl->table.buckets->buckets.length; ++i) { - struct bucket_s *old_b = &tbl->table.buckets->buckets.bucket[i]; - if (old_b->key != NULL) { - struct bucket_s *b; - mps_ld_add(&tbl->table.ld, arena, old_b->key); - b = buckets_find(new_buckets, old_b->key); - assert(b != NULL); /* new table shouldn't be full */ - assert(b->key == NULL); /* shouldn't be in new table */ - *b = *old_b; - if (b->key == key) key_bucket = b; - } - } - - tbl->table.buckets = new_buckets; - return key_bucket; - } + :emphasize-lines: 6 static obj_t table_ref(obj_t tbl, obj_t key) { - struct bucket_s *b = buckets_find(tbl->table.buckets, key); - if (b && b->key != NULL) + 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); @@ -306,52 +190,6 @@ back to a non-address-based version of the computation: here, since it might as well find the bucket containing ``key`` at the same time and return it. -By adding the line:: - - puts("Stale!"); - -after :c:func:`mps_ld_isstale` returns true, we get to see when the -location dependency becomes stale and the table has to be rehashed. - -.. code-block:: none - :emphasize-lines: 21, 23 - - MPS Toy Scheme Example - 10240, 0> (define ht (make-eq-hashtable)) - ht - 10584, 0> (hashtable-set! ht 'one 1) - 10768, 0> ht - #[hashtable (one 1)] - 10768, 0> (gc) - 10792, 1> (hashtable-ref ht 'one #f) - Stale! - 1 - 11080, 1> (hashtable-set! ht 'two 2) - 11264, 1> (gc) - 11288, 2> (hashtable-ref ht 'one #f) - Stale! - 1 - 11576, 2> (hashtable-set! ht 'three 3) - 11760, 2> (hashtable-ref ht 'two #f) - 2 - 11904, 2> (gc) - 11928, 3> (hashtable-ref ht 'one #f) - 1 - 12072, 3> (hashtable-ref ht 'two #f) - Stale! - 2 - 12360, 3> (hashtable-ref ht 'three #f) - 3 - -.. note:: - - In case you're puzzled by the highlighted lines: the symbol - ``'one`` must not have been moved by the collection, and so was - found in the table at the correct location. Thus - :c:func:`mps_ld_isstale` was not called. The symbol ``'two`` did - move in the collection, so it's not found in the table, and that - causes :c:func:`mps_ld_isstale` to be tested. - .. index:: pair: location dependency; thread safety