1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
.. highlight:: none


.. index::
   pair: AMS pool class; design
   single: pool class; AMS design

.. _design-poolams:


AMS pool class
==============

.. mps:prefix:: design.mps.poolams
   pair: AMS pool class; design
   single: pool class; AMS design


Introduction
------------

:mps:tag:`intro` This is the design of the AMS pool class.

:mps:tag:`readership` MM developers.

:mps:tag:`source` design.mps.buffer_, design.mps.trace_, design.mps.scan_,
design.mps.action and design.mps.pool_ [none of these were
actually used -- pekka 1998-04-21]. No requirements doc [we need a
req.mps that captures the commonalities between the products -- pekka
1998-01-27].

.. _design.mps.pool: pool.html
.. _design.mps.scan: scan.html
.. _design.mps.trace: trace.html
.. _design.mps.buffer: buffer.html


Overview
--------

:mps:tag:`overview` This is the design of the AMS (Automatic Mark-and-Sweep)
pool class. The AMS pool is a proof-of-concept design for a mark-sweep
pool in the MPS. It's not meant to be efficient, but it could serve as
a model for an implementation of a more advanced pool (such as EPVM).


Requirements
------------

:mps:tag:`req.mark-sweep` The pool must use a mark-and-sweep GC algorithm.

:mps:tag:`req.colour` The colour representation should be as efficient as
possible.

:mps:tag:`req.incremental` The pool must support incremental GC.

:mps:tag:`req.ambiguous` The pool must support ambiguous references to
objects in it (but ambiguous references into the middle of an object
do not preserve the object).

:mps:tag:`req.format` The pool must be formatted, for generality.

:mps:tag:`req.correct` The design and the implementation should be simple
enough to be seen to be correct.

:mps:tag:`req.simple` Features not related to mark-and-sweep GC should
initially be implemented as simply as possible, in order to save
development effort.

:mps:tag:`not-req.grey` We haven't figured out how buffers ought to work
with a grey mutator, so we use :mps:ref:`.req.correct` to allow us to design a
pool that doesn't work in that phase. This is acceptable as long as we
haven't actually implemented grey mutator collection.


Architecture
------------

Subclassing
...........

:mps:tag:`subclass` Since we expect to have many mark-and-sweep pools, we
build in some protocol for subclasses to modify various aspects of the
behaviour. Notably there's a subclassable segment class, and a
protocol for performing iteration.


Allocation
..........

:mps:tag:`align` We divide the segments in grains, each the size of the
format alignment. :mps:tag:`alloc-bit-table` We keep track of allocated
grains using a bit table. This allows a simple implementation of
allocation and freeing using the bit table operators, satisfying
:mps:ref:`.req.simple`, and can simplify the GC routines. Eventually, this
should use some sophisticated allocation technique suitable for
non-moving automatic pools.

:mps:tag:`buffer` We use buffered allocation, satisfying
:mps:ref:`.req.incremental`. The AMC buffer technique is reused, although it
is not suitable for non-moving pools, but req.simple allows us to do
that for now.

:mps:tag:`extend` If there's no space in any existing segment, a new segment
is allocated. The actual class is allowed to decide the size of the
new segment.

:mps:tag:`no-alloc` Do not support :c:func:`PoolAlloc()`, because we can't support
one-phase allocation for a scannable pool (unless we disallow
incremental collection). For exact details, see design.mps.buffer_.

:mps:tag:`no-free` Do not support :c:func:`PoolFree()`, because automatic pools
don't need explicit free and having it encourages clients to use it
(and therefore to have dangling pointers, double frees, and other
memory management errors.)


Colours
.......

:mps:tag:`colour` Objects in a segment which is *not* condemned (for some
trace) take their colour (for this trace) from the segment.

:mps:tag:`colour.object` Since we need to implement a non-copying GC, we
keep track of the colour of each object in a condemned segment
separately. For this, we use bit tables with a bit for each grain.
This format is fast to access, has better locality than mark bits in
the objects themselves, and allows cheap interoperation with the
allocation bit table.

:mps:tag:`colour.encoding` As to the details, we follow
analysis.non-moving-colour(3), implementing both the alloc-white
sharing option described in
analysis.non-moving-colour.constraint.reclaim.white-free-bit and the
vanilla three-table option, because the former cannot work with
interior pointers. However, the colour encoding in both is the same.

:mps:tag:`ambiguous.middle` We will allow ambiguous references into the
middle of an object (as required by :mps:ref:`.req.ambiguous`), using the
trick in analysis.non-moving-colour.interior.ambiguous-only to speed
up scanning.

:mps:tag:`interior-pointer` Note that non-ambiguous interior pointers are
outlawed.

:mps:tag:`colour.alloc` Objects are allocated black. This is the most
efficient alternative for traces in the black mutator phase, and
:mps:ref:`.not-req.grey` means that's sufficient.

.. note::

    Some day, we need to think about allocating grey or white during
    the grey mutator phase.


Scanning
........

:mps:tag:`scan.segment` The tracer protocol requires (for segment barrier
hits) that there is a method for scanning a segment and turning all
grey objects on it black. This cannot be achieved with a single
sequential sweep over the segment, since objects that the sweep has
already passed may become grey as later objects are scanned.

:mps:tag:`scan.graph` For a non-moving GC, it is more efficient to trace
along the reference graph than segment by segment. It also allows
passing type information from fix to scan. Currently, the tracer
doesn't offer this option when it's polling for work.

:mps:tag:`scan.stack` Tracing along the reference graph cannot be done by
recursive descent, because we can't guarantee that the stack won't
overflow. We can, however, maintain an explicit stack of things to
trace, and fall back on iterative methods (:mps:ref:`.scan.iter`) when it
overflows and can't be extended.

:mps:tag:`scan.iter` As discussed in :mps:ref:`.scan.segment`, when scanning a
segment, we need to ensure that there are no grey objects in the
segment when the scan method returns. We can do this by iterating a
sequential scan over the segment until nothing is grey (see
:mps:ref:`.marked.scan` for details).

:mps:tag:`scan.iter.only` Some iterative method is needed as a fallback for
the more advanced methods, and as this is the simplest way of
implementing the current tracer protocol, we will start by
implementing it as the only scanning method.

:mps:tag:`scan.buffer` We do not scan between ScanLimit and Limit of a
buffer (see :mps:ref:`.iteration.buffer`), as usual.

.. note::

    design.mps.buffer_ should explain why this works, but doesn't.
    Pekka P. Pirinen, 1998-02-11.

:mps:tag:`fix.to-black` When fixing a reference to a white object, if the
segment does not refer to the white set, the object cannot refer to
the white set, and can therefore be marked as black immediately
(rather than grey).


Implementation
--------------

Colour
......

:mps:tag:`colour.determine` Following the plan in :mps:ref:`.colour`, if
``SegWhite(seg)`` includes the trace, the colour of an object is given
by the bit tables. Otherwise if ``SegGrey(seg)`` includes the trace,
all the objects are grey. Otherwise all the objects are black.

:mps:tag:`colour.bits` As we only have searches for runs of zero bits, we use
two bit tables, the non-grey and non-white tables, but this is hidden
beneath a layer of macros talking about grey and white in positive
terms.

:mps:tag:`colour.single` We have only implemented a single set of mark and
scan tables, so we can only condemn a segment for one trace at a time.
This is checked for in condemnation. If we want to do overlapping
white sets, each trace needs its own set of tables.

:mps:tag:`colour.check` The grey-and-non-white state is illegal, and free
objects must be white as explained in
analysis.non-moving-colour.contraint.reclaim.


Iteration
.........

:mps:tag:`iteration` Scan, reclaim and other operations need to iterate over
all objects in a segment. We abstract this into a single iteration
function, even though we no longer use it for reclaiming and rarely
for scanning.

:mps:tag:`iteration.buffer` Iteration skips directly from ScanLimit to Limit
of a buffer. This is because this area may contain
partially-initialized and uninitialized data, which cannot be
processed. Since the iteration skips the buffer, callers need to take
the appropriate action, if any, on it.

.. note::

    ScanLimit is used for reasons which are not documented in
    design.mps.buffer_.


Scanning Algorithm
..................

:mps:tag:`marked` Each segment has a ``marksChanged`` flag, indicating
whether anything in it has been made grey since the last scan
iteration (:mps:ref:`.scan.iter`) started. This flag only concerns the colour
of objects with respect to the trace for which the segment is
condemned, as this is the only trace for which objects in the segment
are being made grey by fixing. Note that this flag doesn't imply that
there are grey objects in the segment, because the grey objects might
have been subsequently scanned and blackened.

:mps:tag:`marked.fix` The ``marksChanged`` flag is set :c:macro:`TRUE` by
:c:func:`amsSegFix()` when an object is made grey.

:mps:tag:`marked.scan` :c:func:`amsSegScan()` must blacken all grey objects on the
segment, so it must iterate over the segment until all grey objects
have been seen. Scanning an object in the segment might grey another
one (:mps:ref:`.marked.fix`), so the scanner iterates until this flag is
:c:macro:`FALSE`, setting it to :c:macro:`FALSE` before each scan. It is safe to
scan the segment even if it contains nothing grey.

:mps:tag:`marked.scan.fail` If the format scanner returns failure (see
protocol.mps.scanning), we abort the scan in the middle of a segment.
So in this case the marksChanged flag is set back to TRUE, because we
may not have blackened all grey objects.

.. note::

    Is that the best reference for the format scanner?

:mps:tag:`marked.unused` The ``marksChanged`` flag is meaningless unless the
segment is condemned. We make it :c:macro:`FALSE` in these circumstances.

:mps:tag:`marked.condemn` Condemnation makes all objects in a segment either
black or white, leaving nothing grey, so it doesn't need to set the
``marksChanged`` flag which must already be :c:macro:`FALSE`.

:mps:tag:`marked.reclaim` When a segment is reclaimed, it can contain
nothing marked as grey, so the ``marksChanged`` flag must already be
:c:macro:`FALSE`.

:mps:tag:`marked.blacken` When the tracer decides not to scan, but to call
:c:func:`SegBlacken()`, we know that any greyness can be removed.
:c:func:`amsSegBlacken()` does this and resets the ``marksChanged`` flag, if
it finds that the segment has been condemned.

:mps:tag:`marked.clever` AMS could be clever about not setting the
``marksChanged`` flag, if the fixed object is ahead of the current
scan pointer. It could also keep low- and high-water marks of grey
objects, but we don't need to implement these improvements at first.


Allocation
..........

:mps:tag:`buffer-init` We take one init arg to set the Rank on the buffer,
just to see how it's done.

:mps:tag:`no-bit` As an optimization, we won't use the alloc bit table until
the first reclaim on the segment. Before that, we just keep a
high-water mark.

:mps:tag:`fill` :c:func:`AMSBufferFill()` takes the simplest approach: it iterates
over the segments in the pool, looking for one which can be used to
refill the buffer.

:mps:tag:`fill.colour` The objects allocated from the new buffer must be
black for all traces (:mps:ref:`.colour.alloc`), so putting it on a black
segment (meaning one where neither ``SegWhite(seg)`` nor
``SegGrey(seg)`` include the trace, see :mps:ref:`.colour.determine`) is
obviously OK. White segments (where ``SegWhite(seg)`` includes the
trace) are also fine, as we can use the colour tables to make it
black. At first glance, it seems we can't put it on a segment that is
grey but not white for some trace (one where ``SegWhite(seg)`` doesn't
include the trace, but ``SegGrey(seg)`` does), because the new objects
would become grey as the buffer's ScanLimit advanced. However, in many
configurations, the mutator would hit a barrier as soon as it started
initializing the object, which would flip the buffer. In fact, the
current (2002-01) implementation of buffers assumes buffers are black,
so they'd better.

:mps:tag:`fill.colour.reclaim` In fact, putting a buffer on a condemned
segment will screw up the accounting in :c:func:`amsSegReclaim()`, so it's
disallowed.

:mps:tag:`fill.slow` :c:func:`AMSBufferFill()` gets progressively slower as more
segments fill up, as it laboriously checks whether the buffer can be
refilled from each segment, by inspecting the allocation bit map. This
is helped a bit by keeping count of free grains in each segment, but
it still spends a lot of time iterating over all the full segments
checking the free size. Obviously, this can be much improved (we could
keep track of the largest free block in the segment and in the pool,
or we could keep the segments in some more efficient structure, or we
could have a real free list structure).

:mps:tag:`fill.extend` If there's no space in any existing segment, the
``segSize`` method is called to decide the size of the new segment to
allocate. If that fails, the code tries to allocate a segment that's
just large enough to satisfy the request.

:mps:tag:`empty` :c:func:`amsSegBufferEmpty()` makes the unused space free, since
there's no reason not to. We have to adjust the colour tables as well,
since these grains were black and now they need to be white (or at
least encoded -G and W).

:mps:tag:`reclaim.empty.buffer` Segments which after reclaim only contain a
buffer could be destroyed by trapping the buffer, but there's no point
to this.


Initialization
..............

:mps:tag:`init` The initialization method :c:func:`AMSInit()` takes three
additional arguments: the format of objects allocated in the pool, the
chain that controls GC timing, and a flag for supporting ambiguous
references.

:mps:tag:`init.share` If support for ambiguity is required, the
``shareAllocTable`` flag is reset to indicate the pool uses three
separate bit tables, otherwise it is set and the pool shares a table
for non-white and alloc (see :mps:ref:`.colour.encoding`).

:mps:tag:`init.align` The pool alignment is set equal to the format
alignment (see design.mps.align).

:mps:tag:`init.internal` Subclasses call :c:func:`AMSInitInternal()` to avoid the
problems of sharing ``va_list`` and emitting a superfluous
``PoolInitAMS`` event.


Condemnation
............

:mps:tag:`condemn.buffer` Buffers are not condemned, instead they are
coloured black, to make sure that the objects allocated will be black,
following :mps:ref:`.colour.alloc` (or, if you wish, because buffers are
ignored like free space, so need the same encoding).


Reclaim
.......

:mps:tag:`reclaim` Reclaim uses either of
analysis.non-moving-colour.constraint.reclaim.white-free-bit (just
reuse the non-white table as the alloc table) or
analysis.non-moving-colour.constraint.reclaim.free-bit (copy it),
depending on the ``shareAllocTable`` flag (as set by :mps:ref:`.init.share`).
However, bit table still has to be iterated over to count the free
grains. Also, in a debug pool, each white block has to be splatted.


Segment merging and splitting
.............................

:mps:tag:`split-merge` We provide methods for splitting and merging AMS
segments. The pool implementation doesn't cause segments to be split
or merged -- but a subclass might want to do this (see
:mps:ref:`.stress.split-merge`). The methods serve as an example of how to
implement this facility.

:mps:tag:`split-merge.constrain` There are some additional constraints on
what segments may be split or merged:

- :mps:tag:`split-merge.constrain.align` Segments may only be split or
  merged at an address which is aligned to the pool alignment as well
  as to the arena grain size.

  :mps:tag:`split-merge.constrain.align.justify` This constraint is implied
  by the design of allocation and colour tables, which cannot
  represent segments starting at unaligned addresses. The constraint
  only arises if the pool alignment is larger than the arena
  alignment. There's no requirement to split segments at unaligned
  addresses.

- :mps:tag:`split-merge.constrain.empty` The higher segment must be empty.
  That is, the higher segment passed to :c:func:`SegMerge()` must be empty,
  and the higher segment returned by :c:func:`SegSplit()` must be empty.

  :mps:tag:`split-merge.constrain.empty.justify` This constraint makes the
  code significantly simpler. There's no requirement for a more
  complex solution at the moment (as the purpose is primarily
  pedagogic).

:mps:tag:`split-merge.fail` The split and merge methods are not proper
anti-methods for each other (see
design.mps.seg.split-merge.fail.anti.no_). Methods will not reverse the
side-effects of their counterparts if the allocation of the colour and
allocation bit tables should fail. Client methods which over-ride
split and merge should not be written in such a way that they might
detect failure after calling the next method, unless they have reason
to know that the bit table allocations will not fail.

.. _design.mps.seg.split-merge.fail.anti.no: seg.html#design.mps.seg.split-merge.fail.anti.no


Testing
-------

:mps:tag:`stress` There's a stress test, MMsrc!amsss.c, that does 800 kB of
allocation, enough for about three GCs. It uses a modified Dylan
format, and checks for corruption by the GC. Both ambiguous and exact
roots are tested.

:mps:tag:`stress.split-merge` There's also a stress test for segment
splitting and merging, MMsrc!segsmss.c. This is similar to amsss.c --
but it defines a subclass of AMS, and causes segments to be split and
merged. Both buffered and non-buffered segments are split / merged.


Notes
-----

:mps:tag:`addr-index.slow` Translating from an address to and from a grain
index in a segment uses macros such as :c:macro:`AMS_INDEX` and
:c:macro:`AMS_INDEX_ADDR`. These are slow because they call :c:func:`SegBase()` on
every translation -- we could cache that.

:mps:tag:`grey-mutator` To enforce the restriction set in :mps:ref:`.not-req.grey`
we check that all the traces are flipped in :c:func:`amsSegScan()`. It would
be good to check in :c:func:`amsSegFix()` as well, but we can't do that,
because it's called during the flip, and we can't tell the difference
between the flip and the grey mutator phases with the current tracer
interface.