-
Notifications
You must be signed in to change notification settings - Fork 0
GCC: LDF AP DUM RAP explained.
Краткое объяснение того, как на самом деле работает это всё.
Во-первых, там нормальный статический скоупинг, точно такой же, как в Питоне. Никакой левой динамики.
%e (environment pointer) указывает на текущий activation record, я их будут называть фреймами. Они специально отделены от control stack, чтобы они могли жить после возвращения из функции.
LDF создаёт Closure(addr, %e), это как бы контекст для вызова функции.
AP создаёт новый фрейм: Frame(parent=closure.env, locals) где все локальные переменные - на самом деле параметры функции, сохраняет текущий %e и адрес возврата в control stack, присваивает в %e новый фрейм и переходит по closure.addr. Теперь наш энвайронмент это фрейм вызова некоей функции, а его парент -- это фрейм в котором она была объявлена (а вовсе не вызывающий фрейм!).
Например, вызвали мы функцию f(x=3, y=5). Её фрейм это тупл с этими вот двумя параметрами. Дальше, исполняя её мы видим "return lambda z: x + y + z". В этот момент мы делаем LDF, указывая ему адрес кода лямбды, он запоминает текущий фрейм и возвращает получившуюся хрень.
Когда кто нибудь вызовет командой AP, у неё создастся новый фрейм с одним параметром z, и парентом как тем фреймом где мы её создали, так что она сможет сделать LD 0 0; LD 1 0; LD 1 1; ADD; ADD; RTN.
Заметьте, что это целиком и полностью обязанность вызывающего подготовить правильный фрейм с нужным количеством переменных и прочего.
Вот, теперь при чём тут RAP (recursive AP). Если б у нас не было мутирующей ST, то нам бы пришлось делать рекурсию через Y-combinator, потому что мы никак не могли бы сказать функции как её нужно вызвать.
Вместо этого мы разделяем AP на две части, DUM + AP. DUM создаёт новый dummy фрейм и позволяет что-то сделать как бы в нём, RAP проверяет что она вызвалась после DUM, заполяет параметры и переходит на нужный адрес. На самом деле мы только хотим делать LDF между ними.
Пример из спецификации,
DUM 2 ; создали фрейм с двумя слотами для параметров
LDC 2 ; это первый параметр, его можно было бы загрузить до DUM
LDF step ; это второй параметр
LDF init ; функция, которую нужно вызвать.
RAP 2
В результате такой махинации parent frame функции step оказывается фреймом функции init. То есть это как если бы мы сделали
def init(2):
def step()
...
как бы создав контекст для вызова функции step уже внутри вызова init и сделав его параметром/локальной переменной. Так что внутри вызова step, LD 1 0 = 2 и LD 1 1 = step.
Да, ну и когда мы делаем RAP, мы не создаём новый фрейм конечно, мы проверяем что "будующий парент фрейм" который нам сделала LDF init это фрейм инита и тупо продолжаем работать в нём.
Вот как-то так.