emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[EXPERIMENT] Emacs with the SpiderMonkey garbage collector


From: Pip Cet
Subject: [EXPERIMENT] Emacs with the SpiderMonkey garbage collector
Date: Thu, 23 Nov 2017 19:01:02 +0000

Hello everyone,

I'm typing this in a version of Emacs that uses the SpiderMonkey
(Mozilla's JavaScript engine) garbage collector, which I've just
succeeded in starting for the first time.  It's an experiment, and I'm
perfectly happy to abandon it and leave with what I've learned; but I
think it's an interesting experiment, and it might help those actually
wanting to use a different garbage collector in the official version
of Emacs. Most of the code is available at
https://github.com/pipcet/emacs/tree/c%2B%2B (This code won't work
as-is yet, I'm afraid. I'm trying to figure out how to get the gnulib
files to build for C++. It's also missing some very recent changes.
I'm hoping to fix that soon, but if you actually want to try things,
it might be best to contact me by email. Also, if anyone could help me
find a better place to host free software, that would be very much
appreciated).

Again, this is an experiment. It's currently slow, unstable, contains
known bugs, will not work except on GNU/Linux with X (and with a
specific hacked version of the SpiderMonkey library), leaks memory,
and provides no practical advantages over official Emacs. I'm writing
this now because I'm trying to decide how much more time to spend on
it, and would appreciate comments and feedback. And, of course,
questions!

The main difference between the current mark-and-sweep garbage
collector and the SpiderMonkey garbage collector is that the latter is
a copying garbage collector and precise, meaning that objects may
change address during GC and that only typed values, not memory
locations that might or might not be, must be traced.  A minor
difference is that GC can happen at any time the code is in the
JavaScript API, meaning that we can no longer get away with stretches
of code that "never GC".

I was hoping to find some bugs in official Emacs during this process,
but have been mostly[1] unsuccessful--I have found bugs, but only ones
introduced by my previous changes.

Let me describe in detail some of the changes I made and how I made them:

1. Make Emacs compile with CC="g++" CFLAGS="-fpermissive"

This was harder than it sounds. In fact, I ended up writing a simple
parser for "Emacs C", the unpreprocessed source code as it appears in
the repository, and performing the necessary replacements
automatically.  As a side effect, declarations of the form

    int x, y;

are rewritten to

    int x; int y;

2. Replace Lisp_Object in the source code

My plan was to use JS::Value objects instead of Lisp_Object.

In order to properly use SpiderMonkey, stack objects must be rooted:
I've decided to use separate types for (a) stack values and values in
structs that live on the stack (b) values on the heap (c) values on
structs that live on the heap (d) return values (e) function
arguments. (In order, ELisp_Value, ELisp_Heap_Value,
ELisp_Struct_Value, ELisp_Return_Value, ELisp_Handle).  While
Lisp_Object values need to be protected, pointers to Lisp_Object need
not, so there is a simple ELisp_Pointer type (which is currently
bloated because it determines at run time whether it's pointing to an
ELisp_Value or an ELisp_Struct_Value), as well as vector (resizeable
arrays) and array (fixed size) types.

Luckily, I was able to modify the C-to-C++ conversion script to
determine automatically which of the replacement types to use for most
of the ~10k instances of "Lisp_Object" in the Emacs source code. In
fact, I decided to keep this approach, modifying the C source code as
little as necessary, then turning each .c or .h file in the src
directory into a C++ .c.cc or .h.hh file, then (ideally) not modifying
the resulting C++ files.  It's currently not quite clean that way.

3. Replace lisp.h

The replacement, jslisp.h.hh, defines ELisp_Value etc. to be C++ types
based on SpiderMonkey's JS::Value, which can be a non-NaN double, or
an NaN-boxed object pointer, 32-bit integer[2], undefined, null, or a
JavaScript symbol or string.  I'm only using doubles, integers, and
JavaScript objects. (So strings and symbols are JavaScript objects,
including Qnil). Each object points (using the JS_GetPrivate pointer)
to an unmovable structure in memory, which in turn contains a copy of
the JavaScript value that represents it. This combines the
disadvantages of a moving and a non-moving garbage collector, but it
was good enough for this experiment.

ELisp_Value, for example, is a rooted type which has a non-trivial
constructor which registers the JS::Value it contains in a "root list"
and a destructor that removes it. In ordinary code, you can otherwise
use it much like a Lisp_Object.

Apart from beginning with a JavaScript value, the actual
constant-address structures are mostly unchanged (I moved some
Lisp_Object struct members that previously weren't GC'd (because they
didn't need to be) to the pseudovector Lisp-Object area so I could
trace them).

4. Replace alloc.c

Most of alloc.c is married to the current garbage collector and needed
to be replaced or simplified, in order to leave memory management to
SpiderMonkey.

Instead, a new file, js.c.cc, contains the new rooting/tracing code:
it registers a hook with SpiderMonkey's garbage collector which
traces, directly or indirectly, all Emacs data except for stack
values, which are traced by SpiderMonkey.

5. Stack unwinding

Emacs uses setjmp()/longjmp(). While I think this code can be
converted to use C++ exceptions instead, I decided it would be easier
to make stack unwinding work with SpiderMonkey. The problem is
destructors of intervening stack frames are not called when unwinding
the stack, so we must find and destroy objects in unwind_to_catch.
This turns out to be easily possible, though we violate the
SpiderMonkey API by accessing fields in a private structure: we save a
stack pointer in the struct handler structure, then compare it to the
current stack pointer upon entering unwind_to_catch.  We then walk the
root lists to find all rooted objects that live in the intervening
stack region and destroy them (and do the same for auto-rooted
vectors, which work the same way but use slightly different code).

6. Calling convention

The usual SpiderMonkey calling convention is that functions do not
return GC types; their arguments are "handles", essentially read-only
pointers to JS::Values.  I decided to return JS::Value objects
directly (except for being wrapped in another class), which opens up
 a race condition:  If f1 and f2 are functions returning
ELisp_Return_Type values, it's illegal to call another function g as
g(f1(...), f2(...)).  f1's return value will not be traced if f2
triggers a GC, so if it is moved there will be a segfault (or worse).
It should be possible to further extend my C-to-C++ script to deal
with this by automatically assigning ELisp_Value temporaries in this
situation. I also decided to pass function arguments as JS::Value
objects, rooting them in the callee instead. Both of these decisions
are open to revision.

7. Misc

Some unions were turned into structs in order to ease tracing them.
Some structs had to be duplicated into a stack and a heap version.
Many previously-unrooted (again, because they didn't need to be)
objects were staticpro'd or added directly to the tracing code.
ELisp_Pointer, a data type representing a pointer to a JS::Value, was
modified to require explicitly-named methods rather than operator
overloading to catch bugs. This introduced new bugs. Finally, after
much debugging, Emacs showed me a usable frame.

8. What now?

While I don't think it's right to have SpiderMonkey-specific code in
Emacs, we don't need to: there's pretty much an automatic API between
Emacs and its garbage collector that's good enough for SpiderMonkey,
but can be trivially implemented by the existing mark-and-sweep
garbage collector. I'd like to make that work, by making as little
code as possible depend on the innards of ELisp_Value etc.

I do not advocate switching to this garbage collection mechanism in
the official Emacs, converting the official Emacs to C++, or renaming
all Lisp_Objects in the official Emacs. I do advocate making the
official Emacs compile with G++ with the -fpermissive option, to help
further experiments. I also think that if there are other ways to make
it easier in the future to switch to a more complicated garbage
collector, we should investigate them, but I need to think about this
more.

The C-to-C++ converter seems potentially useful for other projects (my
initial approach was to try coccinelle, but I never got that to work
right), and should be extended to provide temporary variables
automatically, at which point we can change the calling convention
back to one that uses handles for read-only arguments.

Using JSObject structures for everything is wasteful, particularly in
the case of cons cells, which should require only 16 bytes each. I
think it should be possible to modify SpiderMonkey to assign a unique
tag to cons cells, allowing us to get them down to 24 bytes (car, cdr,
and a hash value (we can no longer use the address because that might
change).

I'd like to get away from the dual
constant-address-structure/pointer-only-JSObject approach. We could
use JSObjects to store rarely-needed properties as JavaScript
properties, and store only commonly-used data in the private
structure. In some cases, we can forego a private structure entirely
(cons cells).

9. Unimplemented

This list is incomplete:
 - non-X environments
 - non-GNU/Linux environments
 - weak hash tables
 - finalizers
 - finalizing markers
 - threads (it's unclear to me whether this is possible)
 - modules
 - images and sounds
 - debugging/backtraces
 - dumping (I'm currently using CANNOT_DUMP=yes. Is that supposed to
work? Because it doesn't without a few changes to the initial Lisp
files.) (Again, I'm not sure this will ever work).
 - reduce warnings (-fpermissive produces copious warnings, most of
which are valid and need to be fixed in the code. Right now, I'm
ignoring them as long as the result works.)
 - remove -fpermissive
 - signal handlers need to be protected specially

In the C-to-C++ converter:
 - operator precedence
 - global (not per-chunk) data, such as function prototypes
 - performance

[1] - there's one place that uses "false" for "NULL", and garbage
collection of markers is O(n^2) in the number of markers per buffer,
which means it tends to dominate GCs in some scenarios (including my
typical usage).  Both are trivial to fix.
[2] - JavaScript doesn't distinguish integers from floating-point
numbers, but SpiderMonkey does. This is relevant because Emacs
sometimes uses a floating-point argument to mean something different
from the equivalent integer argument.

Sorry this got quite long! Any comments, private or public, would be
appreciated.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]