Skip to content

GCC: LDF AP DUM RAP explained.

fj128 edited this page Jul 27, 2014 · 2 revisions

Краткое объяснение того, как на самом деле работает это всё.

Во-первых, там нормальный статический скоупинг, точно такой же, как в Питоне. Никакой левой динамики.

%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 это фрейм инита и тупо продолжаем работать в нём.

Вот как-то так.

Clone this wiki locally