17.7 Декомпилированный байт-код

Люди не пишут байт-код; эта задача остается за байт-компилятором. Но мы предоставляем декомпилятор, чтобы удовлетворить кошачью любознательность. Декомпилятор преобразует байт-скомпилированный код в удобочитаемый человеком вид.

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

В дополнение к стеку, функции байт-кода могут использовать, связывать и устанавливать обычные переменные Lisp, передавая значения между переменными и стеком.

Команда: disassemble object &optional buffer-or-name

Эта команда отображает декомпилированный код для object. При интерактивном использовании или если buffer-or-name равен nil или опущен, вывод помещается в буфер с именем *Disassemble*. Если buffer-or-name не равен nil, он должен быть буфером или именем существующего буфера. Тогда вывод помещается туда, в точку, и точка остается перед выводом.

Аргумент object может быть именем функции, лямбда-выражением (см. Лямбда-выражения) или объектом байт-кода (см. Объекты функций замыкания). Если это лямбда-выражение, disassemble компилирует его и декомпилирует полученный скомпилированный код.

Вот два примера использования функции disassemble. Мы добавили пояснительные комментарии, чтобы помочь вам соотнести байт-код с исходным кодом Lisp; они не появляются в выводе disassemble.

(defun factorial (integer)
  "Вычислить факториал целого числа."
  (if (= 1 integer) 1
    (* integer (factorial (1- integer)))))
     ⇒ factorial

(factorial 4)
     ⇒ 24

(disassemble 'factorial)
     -| байт-код для factorial:
 doc: Вычислить факториал целого числа.
 args: (arg1)

0   dup                   ; Получить значение integer и
                          ;   поместить его в стек.
1   constant 1            ; Поместить 1 в стек.
2   eqlsign               ; Извлечь две верхние значения из стека, сравнить
                          ;   их и поместить результат в стек.
3   goto-if-nil 1         ; Извлечь и проверить верхний элемент стека;
                          ;   если nil, перейти к 1, иначе продолжить.
6   constant 1            ; Поместить 1 в верхний элемент стека.
7   return                ; Вернуть верхний элемент стека.
8:1 dup                   ; Поместить значение integer в стек.
9   constant factorial    ; Поместить factorial в стек.
10  stack-ref 2           ; Поместить значение integer в стек.
11  sub1                  ; Извлечь integer, уменьшить значение,
                          ;   поместить новое значение в стек.
12  call     1            ; Вызвать функцию factorial, используя первый
                          ;   (т.е. верхний) элемент стека в качестве аргумента;
                          ;   поместить возвращенное значение в стек.
13  mult                  ; Извлечь две верхние значения из стека, умножить
                          ;   их и поместить результат в стек.
14  return                ; Вернуть верхний элемент стека.

Функция silly-loop несколько сложнее:

(defun silly-loop (n)
  "Вернуть время до и после N итераций цикла."
  (let ((t1 (current-time-string)))
    (while (> (setq n (1- n))
              0))
    (list t1 (current-time-string))))
     ⇒ silly-loop

(disassemble 'silly-loop)
     -| байт-код для silly-loop:
 doc: Вернуть время до и после N итераций цикла.
 args: (arg1)

0   constant current-time-string  ; Поместить current-time-string в стек.
                                  ;   в верхний элемент стека.
1   call     0            ; Вызвать current-time-string без аргументов,
                          ;   поместить результат в стек как t1.
2:1 stack-ref 1           ; Получить значение аргумента n
                          ;   и поместить его в стек.
3   sub1                  ; Вычесть 1 из верхнего элемента стека.
4   dup                   ; Дублировать верхний элемент стека; т.е. скопировать верхний
                          ;   элемент стека и поместить копию в стек.
5   stack-set 3           ; Извлечь верхний элемент стека,
                          ;   и установить n равным этому значению.

;; (По сути, последовательность dup stack-set копирует верхний элемент
;; стека в значение n без его извлечения.)

7   constant 0            ; Поместить 0 в стек.
8   gtr                   ; Извлечь две верхние значения из стека,
                          ;   проверить, больше ли n 0
                          ;   и поместить результат в стек.
9   goto-if-not-nil 1     ; Перейти к 1, если n > 0
                          ;   (это продолжает цикл while)
                          ;   иначе продолжить.
12  dup                   ; Поместить значение t1 в стек.
13  constant current-time-string  ; Поместить current-time-string в стек.
                                  ;   в верхний элемент стека.
14  call     0            ; Снова вызвать current-time-string.
15  list2                 ; Извлечь две верхние элемента из стека, создать
                          ;   из них список и поместить его в стек.
16  return                ; Вернуть значение верхнего элемента стека.