5.5. Компиляция
535
(assign val (op compiled-procedure-entry) (reg proc))
(goto (reg val))
При такой реализации типа связи return компилятор порождает код, обладающий
свойством хвостовой рекурсии. Вызов процедуры, если это последнее действие в теле
процедуры, приводит к простой передаче управления, когда на стек ничего не кладется.
Предположим, однако, что мы реализовали случай вызова процедуры с типом связи
return и целевым регистром val так, как показано выше для случая с цел ью не-val.
Хвостовая рекурсия оказалась бы уничтожена. Наша система по-прежнему вычисляла бы
то же значение для всех выражений. Однако при к аждом вызове процедур мы сохраняли
бы continue, а после вызова возвращались бы для (ненужного) восстановления. В
гнезде рекурсивных вызовов эти дополнительные сохранения накапливались бы
40
.
При порождении вышеописанного кода для применения процедуры compile-proc-
appl рассматри вает четыре случая, в зависи мости от того, является ли val целевым
регистром, и от того, дан ли нам тип связи return. Обратите внимание: указано, что
эти последовательности команд изменяют все ре гистры, поскольку при выполнении те-
ла процедуры регистрам разрешено меняться к ак угодно
41
. Заметим, кроме того, что в
случае с целевым регистром val и типом связи return говорится, что участок кода
нуждается в continue: хотя в этой двухкомандной последовательности continue яв-
но не упоминается, нам нужно знать, что при входе в ском пилированную процедуру
continue будет содержать правильное значение.
(define (compile-proc-appl target linkage)
(cond ((and (eq? target ’val) (not (eq? linkage ’return)))
(make-instruction-sequence ’(proc) all-regs
‘((assign continue (label ,linkage))
(assign val (op compiled-procedure-entry)
(reg proc))
(goto (reg val)))))
((and (not (eq? target ’val))
(not (eq? linkage ’return)))
(let ((proc-return (make-label ’proc-return)))
(make-instruction-sequence ’(proc) all-regs
‘((assign continue (label ,proc-return))
40
Казалось бы, заставить компилятор порождать код с хвостовой рекурсией — естественная идея. Однако
большинство компиляторов для распространенных языков, включая C и Паскаль, так не делают, и, следова-
тельно, в этих языках итеративный процесс нельзя представить только через вызовы процедур. Сложность
с хвостовой рекурсией в этих языках состоит в том, что их реализации сохраняют на стеке не тол ько адрес
возврата, но еще и аргумен ты процедур и локальные переменные. Реализаци и Scheme, описанные в этой кни-
ге, хранят аргументы и переменные в памяти и подвергают их сборке мусора. Причина использования стека
для переменных и аргументов — в том, что при этом можно избежать сборки мусора в языках, которые не
требуют ее по другим причинам, и вообще считается, что так эффективнее. На самом деле изощренные реали-
зации Лиспа могут хранить аргументы на стеке, не уничтожая хвостовую рекурсию. (Описание можно найти
в Hanson 1990.) Кроме того, ведутся споры о том, правда ли, что выделение памяти на стеке эффективнее,
чем сборка мусора, но тут результат, кажется, зависит от тонких деталей архитектуры компьютера. (См. Appel
1987 и Miller and Rozas 1994, где по этому вопросу высказываются противоположные мнения.)
41
Значением переменной all-regs является список имен всех регистров:
(define all-regs ’(env proc val argl continue))