The purpose of this document is to outline how emacs-ng's native changes work.
sync_ops. Deno provides a system to manage ops. We have a policy that emacs-ng will not add ops, or deal with the ops API. If you want to add native functionality to emacs-ng, you should directly bind the function, like with
lisp_callback and friends.
As these async events execute in the background, we will poll for their completion. In order to integrate with Emacs program loop, we set a timer, calling a function named
js-tick-event-loop. This function is really just a wrapper around Deno's
poll_event_loop. For performance reasons,
js-tick-event-loop can call
poll_event_loop multiple times. The user does have control of this behavior via
Proxies and Garbage Collector (GC) Interoperability#
For example, we will discuss how this code actually works:
const buffer = lisp.get_buffer_create('*foo*'); lisp.set_current_buffer(buffer); lisp.insert("FOOBAR");
The Lisp Object#
lisp.get_buffer_create, it returns a function that does something like:
(...args) => lisp_invoke('get-buffer-create' ...args);
If we can parse the result of
(lambda () (...))) are another special case where additional logic is employed.
lisp_invoke also works in reverse when being called, it will
unproxy the arguments it is passed in order to further pass them to
We expose a special function called
is_proxy in order to tell if an object is a proxy.
When we create a proxy, we need to properly manage it in the lisp garbage collector. We do not want lisp to GC an object out from underneath us. In order to do this, we need to make two considerations for each direction of JS -> Lisp and Lisp -> JS
To prevent the lisp GC from removing objects that JS has a valid reference to, we include them in a special cons called the
js-retain-map. The user does not have direct access to this object. Allowing them to access this cons would allow mutation in a way that could lead to use after free bugs.
WeakRef. This is documented here. Once an object has no outstanding references (besides the WeakRef itself) the WeakRef will return undefined once accessed. We maintain a global array of WeakRefs for all proxies that we sweep every time lisp performs its garbage collection. We map this array to the
Users will notice that lambas will auto convert between JS and elisp. Example:
lisp.run_with_timer(1, lisp.symbols.nil, () => console.log('This works...'));
How does this work under the hood? When we go to create a proxy, we will test if that object is a function. If so, we will include it in another special array for functions. We will then create the following lisp object representing that lambda:
(lambda (&REST) (js--reenter INDEX (make-finalizer (lambda () js--clear INDEX)) REST))
Index is hard-coded per lambda object, i.e.
js--reenter 1 .... Calling this lambda will call js--reenter, which is just calling the function in the array at INDEX. In order to ensure that function will be garbage collected, we add a lisp finalizer, which will clear the lambda from the array upon GC.