![]() Anytime the Garbage collector runs it takes a &mut self, ensuring all objects it created can’t be accessed afterwards. All objects have a lifetime tied to a Context object. The most interesting crates to me are ones that use Rust’s borrow checker to ensure that it is safe to run the collector. I am not going to go into detail here because you can find a great overview of different approaches in this series of blog posts. Rust has some unique offerings that promise to not only make garbage collectors easier to implement, but safer to use as well. As Bob Nystrom said, “garbage collector bugs really are some of the grossest invertebrates out there.” I have done a lot of reading on garbage collectors and they are considered very tricky to get right. This works fine for bootstrapping, but there is no freeing of unused memory. So when the arena goes out of scope, so do all the objects it owns. This works because all objects are “owned” by an arena, and all the lifetimes of objects are tied to the borrow of the that arena. I have not currently implemented a garbage collector for my interpreter, though it doesn’t leak memory. But it looks like that is still a ways off. If specialization is ever stabilized, it will remove hundreds of lines of boilerplate from the code base. Because of this I ended up implementing many of the traits with macros instead of generics. This absence means that anytime you need to specialize for one type you completely lose the use of generics for that function/trait 3. However, I found that in practice generics were less useful than they could have been due to the lack of specialization. Generics are particular useful in conjunction with traits, letting you implement them for a range of types. Generics are a really powerful feature that let you build reusable data structures and help eliminate some boilerplate. For example the function signature above will become (- &optional NUMBER &rest NUMBERS).This makes it easy to use the function in Rust or lisp, and the syntax is much cleaner then the DEFUN macro used in the the Emacs C code. The type signature of the Rust function also gets converted to the type signature in lisp Option types become &optional and slices become &rest. This macro creates a wrapper around the function that transforms lisp objects into Rust types and back, handling any type errors along the way. Therefore, when I was initially defining object type as a union. However, because of the language specification that pointers are a full word, you can’t normally use optimizations like NaN-boxing or pointer tagging in Rust. Rust provides a strong candidate in its enums, but you are limited to the representations that they provide. Since you will frequently be boxing and unboxing values, you want these to be both time and space efficient. I ended up needing to make some tweaks to the ordering and structure of the lisp files to support a bytecode-only bootstrap.Ī critical part of any dynamic language is how to represent types at runtime. This is harder for a byte-compiler because you want to expand the macro’s at compile time instead of run time. Macros are often used before they are defined because the interpreter has lazy-macro expansion. All the early elisp files assume that you are using an interpreter. This turned out to be harder than I initially thought. I didn’t want the duplicate work, so I opted to only have a byte code VM and no interpreter. They all provide their own sets of trade-offs, but that also mean that any new feature needs to be implemented up to 3 times. At this point I have a enough of an interpreter that I want to share an update and mention some of the trade-offs and other things I have learned.Įmacs has 3 seperate execution engines: a tree walk interpreter, a Bytecode VM, and ( recently!) native compile. I have not reached that goal yet, but I have bootstrapped several core Emacs lisp files. My goal for this project is to bootstrap bytecomp.el and use the Emacs compiler with my bytecode vm using only stable Rust. This all came to a head, and I decided to write an Emacs Lisp interpreter called rune in Rust. At the same time, I was reading the Rust book and trying to understand the concepts there. I am also been a big fan of Emacs, and this started to get me interested in how it’s interpreter works. ![]() It started with reading Crafting Interpreters and discovering the wonders hidden under the hood of a compiler. About a year ago I was bitten by the PL bug.
0 Comments
Leave a Reply. |
AuthorWrite something about yourself. No need to be fancy, just an overview. ArchivesCategories |