Какие хорошие способы реализации устранения хвостового вызова?

Я написал небольшой интерпретатор Scheme в нечестивом сочетании C/С++, но мне еще предстоит реализовать правильные хвостовые вызовы.

Я знаю классический Чейни по алгоритму MTA, но есть ли другие хорошие способы его реализации? Я знаю, что могу положить стек Схемы в кучу, но это все равно не будет правильным устранением, поскольку стандарт говорит, что нужно поддерживать неограниченное количество активных хвостовых вызовов.

Я также играл с longjmps, но пока думаю, что он будет хорошо работать только для взаимно-рекурсивных вызовов хвоста.

Как основные схемы на основе C реализуют правильную рекурсию хвоста?

Ответы

Ответ 1

Упрощение написания компилятора и виртуальной машины - это регистрация и трамплин вашего переводчика. Поскольку у вас есть интерпретатор, а не компилятор (я полагаю), вам нужна только пара простых преобразований, чтобы получить надлежащую поддержку для вызовов хвоста.

Вам придется сначала написать все в стиле продолжения, который может быть странным думать и делать на C/С++. Дан Фридман ParentheC поможет вам преобразовать высокоуровневую, рекурсивную программу в форму, которая может быть переведена на компьютер C.

В конце вы по существу реализуете простую виртуальную машину, где вместо обычных вызовов функций для выполнения eval, applyProc и т.д. вы передаете аргументы, задавая глобальные переменные, а затем делаете goto для следующего аргумента ( или использовать контур верхнего уровня и счетчик программ)...

return applyProc(rator, rand)

становится

reg_rator = rator
reg_rand = rand
reg_pc = applyProc
return

Таким образом, все ваши функции, которые обычно называют друг друга рекурсивно, сводятся к псевдосети, в которой они являются просто блоками кода, которые не повторяются. Контур верхнего уровня управляет программой:

for(;;) {
  switch(reg_pc) {
    case EVAL:
      eval();
      break;
    case APPLY_PROC:
      applyProc();
      break;
    ...
  }
}

Изменить: Я прошел один и тот же процесс для моего интерпретатора схемы хобби, написанного на JavaScript. Я воспользовался многими анонимными процедурами, но это может помочь в качестве конкретной ссылки. Посмотрите на историю фиксации FoxScheme, начиная с 2011-03-13 (30707a0432563ce1632a) до 2011-03-15 (5dd3b521dac582507086).

Изменить ^ 2: Рекурсия без хвоста будет по-прежнему потреблять память, даже если она не находится в стеке.

Ответ 2

Не зная, что у вас есть, я бы сказал, что самый простой (и самый просветительный) способ сделать это - реализовать компилятор схемы и виртуальную машину от Dybvig "Три модели реализации для схемы".
Я сделал это здесь, в Javascript (там также есть копия Dybvig PDF): https://github.com/z5h/zb-lisp

проверить src/compiler.js: compileCons и реализацию "op-кодов" в src/vm.js

Ответ 3

Если вас интересуют методы внедрения переводчиков, никак не связано с книгой "LiSP - Lisp в маленьких пьесах" Кристиана Квиннека. В нем подробно описаны все аспекты внедрения системы Scheme полный код. Это замечательная книга.

http://www.amazon.com/exec/obidos/ASIN/0521562473/qid=945541473/sr=1-2/002-2995245- 1849825

Но не забудьте ознакомиться с документами на ReadScheme.org.

Раздел

Технология компилятора/методы внедрения и оптимизация http://library.readscheme.org/page8.html

имеется довольно много работ по оптимизации хвостового вызова.

Среди других вы найдете ссылку на тезис Дибвига (классика), который очень хорошо написан. Он объясняет и мотивирует все в очень четко.