Crafting Interpreters
- https://craftinginterpreters.com/contents.html
- https://github.com/munificent/craftinginterpreters
- Стили IR
- CFG
- SSA
- CPS
- Three address code
- Ruby перешел от MRI (tree walking interpreter) в YARV (bytecode interpreter) только в 1.8.
- Expression produces a value, statement produces an effect.
- Semantics = effects on the abstract machine.
- Closures
- Look up "closure conversion" and "lambda lifting" – ways to optimise closures.
- Замыкания часто связаны со сборкой мусора потому что несколько замыканий могут использовать одну и ту же функцию, но они не владеют ей. Нельзя деаллоцировать функцию когда мы деаллоцируем замыкание.
- Каждый объект ObjClosure хранит указатель на функцию и массив upvalues.
- Open upvalue указывает на переменную которая все еще на стеке.
- Closed upvalue указывает на переменную, которая переехала со стека на хип.
- Когда функция завершается и локальная переменная выходит из scope, ее upvalue можно закрыть. OP_POP меняется на OP_CLOSE_UPVALUE.
- Сборка мусора
- Существуют roots – значения, до которых можно добраться напрямую – объекты на стеке и глобальные переменные (хеш-таблица). Также нужно обойти call stack потому что он хранит указатели на замыкания. Также список открытых upvalues нужных для замыканий.
- Mark-sweep
- Изобрал John McCarthy для LISP.
- Mark: начиная с каждого root сделать graph traverse и пометить пройденное.
- Sweep: удалить все непомеченное.
- Простейший способ – запускать сборку сразу перед любой аллокацией.
- Tri-color abstraction
- Throughput – время потраченное исключительно на саму программу (а не на GC).
- Latency – самый долгий непрерывный кусок времени потраченный на GC.
- Хотим максимизировать throughput, минимизировать latency.
- Держать bytesAllocated и nextGC, запускать когда bytesAllocated > nextGC, потом увеличивать nextGC.
- Между push и pop может произойти GC, поэтому даже временные объекты нужно добавлять в стек, в случае GC который использует только стек значений и стек вызовов, но не C стек. Некоторые GC также используют C стек, такие как Boehm-Demers-Wieser.
- Generational GC
- Два участка: nursery (GC зовется часто) и tenured (GC зовется редко). Новые объекты попадают в nursery. GC вызывается часто для nursery. Для каждого выжившего объекта увеличивается tenure, после определенного заранее значения они попадают в tenured.
- Chapter 30: Optimization
- NaN Boxing – способ запихнуть VM value занимающее 16 байтов в естественной имплементации в 8 байтов (double).
- IEEE 754: на 64-битной машине число представлено в виде 1 бита знака, 11 бит экспоненты, 52 бита мантиссы.
- Если все 11 битов экспоненты включены, то это NaN.
- Раньше: struct из enum и union (bool, double, pointer).
- NaN Boxing – способ запихнуть VM value занимающее 16 байтов в естественной имплементации в 8 байтов (double).
#include <stdbool.h> #include <stdio.h>
enum Type { null, boolean, number, object };
struct Object {};
struct Value { enum Type type; union { bool boolean; double number; struct Object* obj; } as; };
int main() { struct Value value; printf("%d", sizeof(value)); // 16 bytes return 0; }```