mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-01-10 21:50:37 -08:00
386 lines
15 KiB
ReStructuredText
386 lines
15 KiB
ReStructuredText
.. mode: -*- rst -*-
|
|
|
|
Stack and register scanning
|
|
===========================
|
|
|
|
:Tag: design.mps.stack-scan
|
|
:Author: Gareth Rees
|
|
:Date: 2014-10-22
|
|
:Status: complete design
|
|
:Revision: $Id$
|
|
:Copyright: See `Copyright and License`_.
|
|
:Index terms: pair: stack and register scanning; design
|
|
|
|
|
|
Introduction
|
|
------------
|
|
|
|
_`.intro`: This is the design of the stack and register scanning
|
|
module.
|
|
|
|
_`.readership`: Any MPS developer; anyone porting the MPS to a new
|
|
platform.
|
|
|
|
_`.overview`: This module locates and scans references in the control
|
|
stack and registers of the *current* thread (the one that has called
|
|
in to the MPS).
|
|
|
|
_`.other`: The thread manager module is responsible for scanning the
|
|
control stack and registers of *other* threads. See
|
|
design.mps.thread-manager.if.scan_.
|
|
|
|
.. _design.mps.thread-manager.if.scan: thread-manager#if-scan
|
|
|
|
_`.origin`: This design was originally proposed in
|
|
mail.richard.2012-08-03.14-36_.
|
|
|
|
.. _mail.richard.2012-08-03.14-36: https://info.ravenbrook.com/mail/2012/08/03/14-36-35/0/
|
|
|
|
|
|
Requirements
|
|
------------
|
|
|
|
_`.req.stack.hot`: Must locate the hot end of the mutator's stack. (This
|
|
is needed for conservative garbage collection of uncooperative code,
|
|
where references might be stored by the mutator on its stack.)
|
|
|
|
_`.req.stack.cold.not`: There is no requirement to locate the cold end
|
|
of the stack. (The mutator supplies this as an argument to
|
|
``mps_root_create_thread()``.)
|
|
|
|
_`.req.stack.platform`: Must support the platform's stack
|
|
conventions.
|
|
|
|
_`.req.stack.platform.full-empty`: The implementation must take into
|
|
account whether the stack is *full* (the stack pointer points to the
|
|
last full location) or *empty* (the stack pointer points to the
|
|
first empty location).
|
|
|
|
_`.req.stack.platform.desc-asc`: The implementation must take into
|
|
account whether the stack is *descending* (the hot end of the stack is
|
|
at a lower address than the cold end) or *ascending* (the hot end of
|
|
the stack is at a higher address than the cold end).
|
|
|
|
_`.req.registers`: Must locate and scan all references in the
|
|
mutator's *root registers*, the subset of registers which might
|
|
contain references that do not also appear on the stack. (This is
|
|
needed for conservative garbage collection of uncooperative code,
|
|
where references might appear in registers.)
|
|
|
|
_`.req.entry`: Should save the mutator's context (stack and registers)
|
|
at the point where it enters the MPS. (This avoids scanning registers
|
|
and stack that belong to the MPS rather than the mutator, leading to
|
|
unnecessary pinning and zone pollution; see job003525_.)
|
|
|
|
.. _job003525: http://www.ravenbrook.com/project/mps/issue/job003525/
|
|
|
|
_`.req.setjmp`: The implementation must follow the C Standard in its
|
|
use of the ``setjmp()`` macro. (So that it is reliable and portable.)
|
|
|
|
_`.req.assembly.not`: The implementation should not use assembly
|
|
language. (So that it can be developed in tools like Microsoft Visual
|
|
Studio that don't support this.)
|
|
|
|
|
|
Design
|
|
------
|
|
|
|
_`.sol.entry-points`: To meet `.req.entry`_, the mutator's registers
|
|
and stack must be recorded when the mutator enters the MPS, if there
|
|
is a possibility that the MPS might need to know the mutator context.
|
|
|
|
_`.sol.entry-points.fragile`: The analysis of which entry points might
|
|
need to save the context (see `.anal.entry-points`_ below) is fragile.
|
|
It might be incorrect now, or become incomplete if we refactor the
|
|
internals of tracing and polling. As a defence against errors of this
|
|
form, ``StackScan()`` asserts that the context was saved, but if the
|
|
client program continues from the assertion, it saves the context
|
|
anyway and continues.
|
|
|
|
_`.sol.registers`: Implementations spill the root registers onto the
|
|
stack so that they can be scanned there.
|
|
|
|
_`.sol.registers.root`: The *root registers* are the subset of the
|
|
callee-save registers that may contain pointers.
|
|
|
|
_`.sol.registers.root.justify`: The caller-save registers will have
|
|
been spilled onto the stack by the time the MPS is entered, so will be
|
|
scanned by the stack scan.
|
|
|
|
_`.sol.setjmp`: The values in callee-save registers can be found by
|
|
invoking ``setjmp()``. This forces any of the caller's callee-save
|
|
registers into either the ``jmp_buf`` or the current stack frame.
|
|
|
|
_`.sol.setjmp.scan`: Although we might be able to decode the jump
|
|
buffer in a platform-dependent way, it's hard to guarantee that an
|
|
uncooperative compiler won't temporarily store a reference in any
|
|
register or stack location. We must conservatively scan the whole of
|
|
both.
|
|
|
|
_`.sol.setjmp.justify`: The [C1990]_ standard specifies that
|
|
``jmp_buf``:
|
|
|
|
is an array type suitable for holding the information needed to
|
|
restore a calling environment. The environment of a call to the
|
|
``setjmp()`` macro consists of information sufficient for a call
|
|
to the ``longjmp()`` function to return execution to the correct
|
|
block and invocation of that block, were it called recursively.
|
|
|
|
We believe that any reasonable implementation of ``setjmp()`` must
|
|
copy the callee-save registers either into the jump buffer or into the
|
|
stack frame that invokes it in order to work as described. Otherwise,
|
|
once the callee-save registers have been overwritten by other function
|
|
calls, a ``longjmp()`` would result in the callee-save registers
|
|
having the wrong values. A ``longjmp()`` can come from anywhere, and
|
|
so the function using ``setjmp()`` can't rely on callee-save registers
|
|
being saved by callees.
|
|
|
|
_`.sol.stack.hot`: We could decode the frame of the function that
|
|
invokes ``setjmp()`` from the jump buffer in a platform-specific way,
|
|
but we can do something simpler (if more hacky) by calling the stub
|
|
function ``StackHot()`` which takes the address of its argument. So
|
|
long as this stub function is not inlined into the caller, then on all
|
|
supported platforms this yields a pointer that is pretty much at the
|
|
hot end of the frame.
|
|
|
|
_`.sol.stack.hot.noinline`: The reason that ``StackHot()`` must not be
|
|
inlined is that after inlining, the compiler might place ``stackOut``
|
|
at a colder stack address than the ``StackContextStruct``, causing the
|
|
latter not to be scanned. See `mail.gdr.2018-07-11.09-48`_.
|
|
|
|
.. _mail.gdr.2018-07-11.09-48: https://info.ravenbrook.com/mail/2018/07/11/09-48-49/0/
|
|
|
|
_`.sol.stack.nest`: We can take care of scanning the jump buffer
|
|
itself by storing it in the same stack frame. That way a scan from the
|
|
hot end determined by `.sol.stack.hot`_ to the cold end will contain
|
|
all of the roots.
|
|
|
|
_`.sol.stack.platform`: As of version 1.115, all supported platforms
|
|
are *full* and *descending* so the implementation in ``StackScan()``
|
|
assumes this. New platforms must check this assumption.
|
|
|
|
_`.sol.xc.alternative`: On macOS, we could use ``getcontext()`` from
|
|
libunwind (see here_), but that produces deprecation warnings and
|
|
introduces a dependency on that library.
|
|
|
|
.. _here: http://stackoverflow.com/questions/3592914/how-can-i-implement-cooperative-lightweight-threading-with-c-on-mac-os-x
|
|
|
|
|
|
Analysis
|
|
--------
|
|
|
|
_`.anal.setjmp`: The [C1990]_ standard says:
|
|
|
|
An invocation of the ``setjmp`` macro shall appear only in one of
|
|
the following contexts:
|
|
|
|
- the entire controlling expression of a selection or iteration
|
|
statement;
|
|
|
|
- one operand of a relational or equality operator with the other
|
|
operand an integral constant expression, with the resulting
|
|
expression being the entire controlling expression of a
|
|
selection or iteration statement;
|
|
|
|
- the operand of a unary ``!`` operator with the resulting
|
|
expression being the entire controlling expression of a
|
|
selection or iteration statement; or
|
|
|
|
- the entire expression of an expression statement (possibly cast
|
|
to ``void``).
|
|
|
|
And the [C1999]_ standard adds:
|
|
|
|
If the invocation appears in any other context, the behavior is
|
|
undefined.
|
|
|
|
_`.anal.entry-points`: Here's a reverse call graph (in the master
|
|
sources at changelevel 189652) showing which entry points might call
|
|
``StackScan()`` and so need to record the stack context::
|
|
|
|
StackScan
|
|
└ThreadScan
|
|
└RootScan
|
|
├traceScanRootRes
|
|
│ └traceScanRoot
|
|
│ └rootFlip
|
|
│ └traceFlip
|
|
│ └TraceStart
|
|
│ ├PolicyStartTrace
|
|
│ │ └TracePoll
|
|
│ │ ├ArenaStep
|
|
│ │ │ └mps_arena_step
|
|
│ │ └ArenaPoll
|
|
│ │ ├mps_alloc
|
|
│ │ ├mps_ap_fill
|
|
│ │ ├mps_ap_fill_with_reservoir_permit
|
|
│ │ ├mps_ap_alloc_pattern_end
|
|
│ │ ├mps_ap_alloc_pattern_reset
|
|
│ │ └ArenaRelease
|
|
│ │ ├mps_arena_release
|
|
│ │ └ArenaStartCollect
|
|
│ │ ├mps_arena_start_collect
|
|
│ │ └ArenaCollect
|
|
│ │ └mps_arena_collect
|
|
│ └TraceStartCollectAll
|
|
│ ├ArenaStep [see above]
|
|
│ ├ArenaStartCollect [see above]
|
|
│ └PolicyStartTrace [see above]
|
|
└rootsWalk
|
|
└ArenaRootsWalk
|
|
└mps_arena_roots_walk
|
|
|
|
So the entry points that need to save the stack context are
|
|
``mps_arena_step()``, ``mps_alloc()``, ``mps_ap_fill()``,
|
|
``mps_ap_fill_with_reservoir_permit()``,
|
|
``mps_ap_alloc_pattern_end()``, ``mps_ap_alloc_pattern_reset()``,
|
|
``mps_arena_release()``, ``mps_arena_start_collect()``,
|
|
``mps_arena_collect()``, and ``mps_arena_roots_walk()``.
|
|
|
|
|
|
Interface
|
|
---------
|
|
|
|
``typedef StackContextStruct *StackContext``
|
|
|
|
_`.if.sc`: A structure encapsulating the mutator context.
|
|
|
|
``Res StackScan(ScanState ss, void *stackCold, mps_area_scan_t scan_area, void *closure)``
|
|
|
|
_`.if.scan`: Scan the stack of the current thread, between
|
|
``stackCold`` and the hot end of the mutator's stack that was recorded
|
|
by ``STACK_CONTEXT_SAVE()`` when the arena was entered. This will
|
|
include any roots which were in the mutator's callee-save registers on
|
|
entry to the MPS (see `.sol.setjmp`_ and `.sol.stack.nest`_). Return
|
|
``ResOK`` if successful, or another result code if not.
|
|
|
|
_`.if.scan.begin-end`: This function must be called between
|
|
``STACK_CONTEXT_BEGIN()`` and ``STACK_CONTEXT_END()``.
|
|
|
|
``STACK_CONTEXT_SAVE(StackContext sc)``
|
|
|
|
_`.if.save`: Store the mutator context in the structure ``sc``.
|
|
|
|
_`.if.save.macro`: This must be implemented as a macro because it
|
|
needs to run in the stack frame of the entry point (if it runs in some
|
|
other function it does not necessarily get the mutator's registers).
|
|
This necessity to have the definition in scope in ``mpsi.c``, while
|
|
also having different definitions on different platforms, requires a
|
|
violation of design.mps.config.no-spaghetti_ in ss.h.
|
|
|
|
.. _design.mps.config.no-spaghetti: config#no-spaghetti
|
|
|
|
``STACK_CONTEXT_BEGIN(Arena arena)``
|
|
|
|
_`.if.begin`: Start an MPS operation that may need to know the mutator
|
|
context (see `.sol.entry-points`_). This macro must be used like this::
|
|
|
|
Res res;
|
|
ArenaEnter(arena);
|
|
STACK_CONTEXT_BEGIN(arena) {
|
|
res = ArenaStartCollect(...);
|
|
} STACK_CONTEXT_END(arena);
|
|
ArenaLeave(arena);
|
|
return res;
|
|
|
|
That is, it must be paired with ``STACK_CONTEXT_END()``, and there
|
|
must be no ``return`` between the two macro invocations.
|
|
|
|
This macro stores the mutator context in a ``StackContext`` structure
|
|
allocated on the stack, and sets ``arena->stackWarm`` to the hot end
|
|
of the current frame (using `.sol.stack.hot`_).
|
|
|
|
``STACK_CONTEXT_END(Arena arena)``
|
|
|
|
_`.if.end`: Finish the MPS operation that was started by
|
|
``STACK_CONTEXT_BEGIN()``.
|
|
|
|
This macro sets ``arena->stackWarm`` to ``NULL``.
|
|
|
|
|
|
Implementations
|
|
---------------
|
|
|
|
_`.impl`: Generic implementation of ``StackScan()`` in ``ss.c`` scans
|
|
the whole area between ``arena->stackWarm`` and the cold end of the
|
|
mutator's stack, implementing `.sol.stack.nest`_ and also the backup
|
|
strategy in `.sol.entry-points.fragile`_.
|
|
|
|
.. figure:: stack-scan-areas.svg
|
|
:align: center
|
|
:alt: Diagram: scanned areas of the stack.
|
|
|
|
|
|
References
|
|
----------
|
|
|
|
.. [C1990]
|
|
International Standard ISO/IEC 9899:1990. "Programming languages — C".
|
|
|
|
.. [C1999]
|
|
International Standard ISO/IEC 9899:1999. "`Programming languages — C <http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf>`_".
|
|
|
|
.. [Fog]
|
|
Agner Fog;
|
|
"`Calling conventions for different C++ compilers and operating systems <http://agner.org/optimize/calling_conventions.pdf>`_";
|
|
Copenhagen University College of Engineering;
|
|
2014-08-07.
|
|
|
|
.. [x86_64_registers]
|
|
Microsoft Corporation;
|
|
"`Caller/Callee Saved Registers <https://msdn.microsoft.com/en-us/library/6t169e9c.aspx>`_".
|
|
|
|
|
|
Document History
|
|
----------------
|
|
|
|
- 2014-10-22 GDR_ Initial draft.
|
|
|
|
- 2016-03-03 RB_ Reorganised based mostly on `.sol.stack.hot`_ and
|
|
`.sol.stack.nest`_.
|
|
|
|
.. _GDR: http://www.ravenbrook.com/consultants/gdr/
|
|
.. _RB: http://www.ravenbrook.com/consultants/rb/
|
|
|
|
|
|
Copyright and License
|
|
---------------------
|
|
|
|
Copyright © 2014-2016 Ravenbrook Limited <http://www.ravenbrook.com/>.
|
|
All rights reserved. This is an open source license. Contact
|
|
Ravenbrook for commercial licensing options.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are
|
|
met:
|
|
|
|
#. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
#. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
#. Redistributions in any form must be accompanied by information on how
|
|
to obtain complete source code for this software and any
|
|
accompanying software that uses this software. The source code must
|
|
either be included in the distribution or be available for no more than
|
|
the cost of distribution plus a nominal fee, and must be freely
|
|
redistributable under reasonable conditions. For an executable file,
|
|
complete source code means the source code for all modules it contains.
|
|
It does not include source code for modules or files that typically
|
|
accompany the major components of the operating system on which the
|
|
executable file runs.
|
|
|
|
**This software is provided by the copyright holders and contributors
|
|
"as is" and any express or implied warranties, including, but not
|
|
limited to, the implied warranties of merchantability, fitness for a
|
|
particular purpose, or non-infringement, are disclaimed. In no event
|
|
shall the copyright holders and contributors be liable for any direct,
|
|
indirect, incidental, special, exemplary, or consequential damages
|
|
(including, but not limited to, procurement of substitute goods or
|
|
services; loss of use, data, or profits; or business interruption)
|
|
however caused and on any theory of liability, whether in contract,
|
|
strict liability, or tort (including negligence or otherwise) arising in
|
|
any way out of the use of this software, even if advised of the
|
|
possibility of such damage.**
|