Forth и другие саморасширяющиеся системы программирования Locations of visitors to this page
Текущее время: Пт мар 29, 2024 03:59

...
Google Search
Forth-FAQ Spy Grafic

Часовой пояс: UTC + 3 часа [ Летнее время ]




Начать новую тему Ответить на тему  [ Сообщений: 3 ] 
Автор Сообщение
 Заголовок сообщения: Портирование Форта. Ч2. Загадочный DOES>
СообщениеДобавлено: Сб ноя 07, 2009 22:17 
Не в сети
Moderator
Moderator
Аватара пользователя

Зарегистрирован: Чт май 04, 2006 00:53
Сообщения: 5062
Откуда: был Крым, теперь Новосибирск
Благодарил (а): 23 раз.
Поблагодарили: 63 раз.
Автор: Бред Родригес
Дата публикации: 1993
оригинал статьи взят MOVING FORTH
Перевел: mOleg


Загадочный DOES>

Что есть поле кода?

Концепция DOES> выглядит наиболее непонятной и даже мистической в Форте. DOES> так же один из наиболее мощных механизмов Форта, который в большинстве случаев заменяет объектно-ориентированное программирование. Действие и мощность DOES> основаны на замечательной инновации Форта – поле кода.

«Тело» Форт-определения состоит из двух частей: поле кода и поле параметров. Вы можете думать об этих полях различным образом:

* поле кода – это «действие» производимое этим Форт-словом, а поле параметров – это данные, над которыми выполняется данное действие.

* поле кода с точки зрения ассемблерного программиста – это подпрограммный вызов, а поле параметров – это параметры (это может быть том числе инлайн код) находящиеся после CALL.

* поле кода с точки зрения ООП программиста – это единственный «метод» для этого «класса» слов, а поле параметров содержит «переменную экземпляра» для этого конкретного слова.

Обычные особенности проявляются во всех перечисленных взглядах на Форт-слово:

* поле кода всегда вызывается с как минимум одним аргументом, а именно, адресом поля параметров исполняемого слова. Поле параметров может содержать любое количество параметров.

* Имеется сравнительно немного индивидуальных действий, на которые ссылается поле кода. Каждое из этих действий широко распространено (за исключением низкоуровневых слов, как станет ясно позднее). Вернемся к примеру слова ENTER, описанного в предыдущей части статьи: это обычная подпрограмма используется всеми двоеточными определениями внутри Форт-системы.

* Интерпретация поля параметров полностью определяется содержимым поля кода, то есть, каждое поле кода ожидает, что поле параметров содержит определенный вид данных.

Типичное Форт-ядро изначально содержит некоторое количество подпрограмм, на которые ссылается поля кода слов.

Поле кода Поле параметров
Подпрограмма содержимое

ENTER последовательность интерпретируемых токенов
DOCON значение константы
DOVAR массив для произвольного набора данных
DOVOC информация словаря (варьируется в зависимости от реализации)

Форт-программа не ограничена приведенным набором подпрограмм полей кода, это свойство делает Форт очень мощным. Программист может определять новые подпрограммы полей кода, и новые соответствующие поля параметров. В терминах объектно-ориентированной парадигмы: новые «классы» и «методы» могут быть созданы (хотя каждый класс имеет только один метод. <прим.переводчика: это не совсем так, есть VALUE переменные – у них три метода, есть варианты решения слов с произвольным количеством полей кода>) . И подобно всем Форт-словам , действия полей кода могут быть определены как на ассемблере, так и на уровне двоеточных слов.

Для понимания механизма работы поля кода, и того, как параметры передаются, мы сначала посмотрим на ассемблерный вариант (маш.код). Давайте начнем с косвенного шитого кода, он проще для понимания, и затем посмотрим как модифицируется логика в прямом и подпрограммном ШК. Затем мы рассмотрим, как может быть реализованы те же действия на высокоуровневом Форт-коде.

Косвенный шитый код
Фортописатели иногда несколько непоследовательны в используемой терминологии, поэтому я определю собственные термины, используя форт с косвенным шитым кодом (Рис.1). Заголовок содержит словарную информацию, и он не вовлечен в исполнение Форт-кода. Тело – это «рабочая» часть слова – состоит из поля кода фиксированной длины, и поля параметров произвольной длины. Для любого взятого слова расположение в памяти идут в аналогичном порядке: сначала поле кода (CFA), затем поле параметров (PFA). CFA слова – это адрес в памяти, по которому расположено поле кода слова. Не должно возникать проблемы с пониманием содержимого поля кода в косвенном ШК, которое содержит другой адрес. Чтобы быть точным, содержимое поля кода – это адрес фрагмента машинного кода, расположенного где-то в памяти. Я буду ссылаться на него, как на адрес кода или CА. Позднее, когда обсуждение дойдет до систем с прямым и подпрограммным ШК, я так же буду ссылаться на «содержимое поля кода», что будет включать в себя больше, нежели просто адрес кода.

Изображение

Действия в машинном коде.

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

1 CONSTANT UN
2 CONSTANT DEUX
3 CONSTANT TROIS

Исполнение слова UN положит значение 1 на вершину стека данных. Исполнение DEUX – положит 2 и так далее. (Не путайте стек данных с полем параметров – это разные вещи).

В Форт-ядре существует единственное слово, называемое CONSTANT. Это не слово определяющее тип константа – это высокоуровневое определение. CONSTANT является определяющим словом: оно создает новое слово в текущем словаре Форт-системы. Выше, мы создали три слова-константы: UN, DEUX, и TROIS(вы можете считать это «экземплярами класса» CONSTANT). Каждое из трех слов будет иметь собственные поля кода, указывающий на один и тот же фрагмент машинного кода, который выполняет действие слова CONSTANT.

Что должен делать этот фрагмент кода? На Рис.2 представлена картина распределения слов в памяти Форт-системы. Все три слова ссылаются на общую подпрограмму действия. Слова различаются только своими полями параметров, которые в данном случае просто содержать значения констант («переменная экземпляра» в объектной терминологии). Таким образом, действие у этих трех заключается в извлечении содержимого поля параметров, и сохранения его в стеке данных.

Изображение

Для написания фрагмента машинного кода мы должны знать, как найти нужное поле параметров, то есть адрес после того, как адресный интерпретатор передал управление машинному коду. То есть, как PFA передается в подпрограмму машинного кода? Это, в свою очередь, требует знания того, как адресный интерпретатор NEXT реализован на конкретной системе, а это варьируется от системы к системе. Для написания действия в машинном коде мы должны понимать как устроен NEXT.

Вариант с косвенным шитым кодом описан в первой части в виде псевдокода. Есть одна реализация для 6809 с использованием Y в качестве IP, и X=W.

NEXT: LDX ,Y++ ; (IP) -> W, и IP+2 -> IP
JMP [,X] ; (W) -> temp, JMP (temp)

Предположим, что в вызывающем высокоуровневом коде

... SWAP DEUX + ...

с указателем интерпретации (IP) указывающим на «инструкцию» DEUX в момент исполнения кода NEXT (это будет конец выполнения слова SWAP). Рисунок 3 отображает что происходит. IP (регистр Y) указывает на токен в высокоуровневом определении, на ячейку памяти, которая содержит адрес Форт-слова DEUX. Точнее, этот токен содержит адрес поля кода слова DEUX. Далее, когда мы извлекаем ячейку используя Y (при этому происходит увеличение Y, таким образом, что он указывает на следующий токен), мы извлекаем CFA слово DEUX. Этот CFA попадает в регистр W (X), таким образом W теперь указывает на начало поля кода. Содержимое этого поля представляет собой адрес машинного кода. Мы можем извлечь содержимое этой ячейки и перейти на машинный код с помощью единственной команды 6809. Это оставляет регистр X неизмененным, а W продолжает указывать на CFA слов DEUX. Таким образом, используя содержимое регистра W, а так же зная размер поля кода (в нашем случае 2 байта) легко попасть на начало поля параметров.

Изображение

Значит, фрагмент машинного кода должен только добавить число 2 к значению регистра W, извлечь из полученного адреса значение, и поместить его на вершину стека данных. Этот фрагмент кода часто называется DOCON:

DOCON: DD 2,X ; извлечь ячейку по адресу W+2
PSHU D ; вытолкнуть результат на вершину стека данных
NEXT ; передать управление следующему слову

(В этом примере TOS хранилось в памяти) Замечу, что NEXT увеличивает IP на 2, таким образом IP уже указывает на следующую ячейку внутри высокоуровневого определения, то есть на слово “+” в момент, когда DOCON выполняет NEXT.

Обычно, Форты с косвенным ШК оставляют адрес поля параметров или что-то «подобное» в регистре W. В этом случае, W содержит CFA, которое в этой Форт-реализации всегда PFA-2. В последствии каждый класс Форт-слов, за исключением CODE слов, нуждается в использовании PFA, многие реализации NEXT умеют самостоятельно увеличивать значение W, оставляя в нем указатель на PFA. Мы можем так поступить на 6809 с помощью небольшой модификации:

NEXT: LDX ,Y++ ; (IP) -> W, и IP+2 -> IP
JMP [,X++] ; (W) -> temp, JMP (temp), W+2 -> W

Это добавит три дополнительных тактовых цикла NEXT, и оставит адрес поля параметров в W.

Что должны делать подпрограммы полей кода?

W=CFA W=PFA

DOCON: LDD 2,X (6) LDD ,X (5)
PSHU D PSHU D
NEXT NEXT

DOVAR: LEAX 2,X (5) ; нет операции
PSHU X PSHU X
NEXT NEXT

ENTER: PSHS Y PSHS Y
LEAY 2,X (5) LEAY ,X (4, быстрее чем TFR X,Y)
NEXT NEXT

В обмен на три цикла замедления NEXT, код DOCON укоротится на один тактовый цикл, DOVAR станет быстрее на пять циклов, а ENTER на один. Низкоуровневые слова не используют значение в W, поэтому его автоувеличение выигрыша не даст. Выигрыш или проигрыш получился, будет определяться набором исполняемых Форт-слов. Обычное правило: большинство исполняемых слов – это примитивы, а значит, увеличение W в NEXT в общем скорость работы понижает (Есть небольшой выигрыш в памяти, но DOCON, DOVAR, и ENTER описываются лишь один раз, поэтому выигрыш незначителен).

Наилучшее решение, конечно, зависит от процессора. На машинах типа Z80, которые адресуют память только побайтно и не имеют автоинкрементных адресных режимах, часто лучше оставлять W указывающим на IP+1 (последний байт, извлеченный из поля кода). На других процессора, где автоинкремент «бесплатный», удобно, чтобы W указывал на поле параметров.

Помните: решение должно приниматься согласованно. Если NEXT оставляет W указывающим на PFA исполняемого слова, EXECUTE должен действовать аналогично.

Прямой шитый код

Прямой ШК работает подобно косвенному, за исключением того, что вместо хранения адреса машинного кода в поле кода он содержит переход или вызов этого кода. Это увеличивает размер поля кода, к примеру, на 1 байт на 6809 процессоре, но убирает один уровень индексации (косвенности) из кода NEXT.

Выбор между JMP или CALL будет находиться в поле кода зависит от того, как будет определяться адрес поля параметров слова в машинном коде. Для случая перехода (JMP) на машинный кода, многие процессоры требуют, чтобы адрес перехода был в регистре. Например, косвенный переход на i8086 – это JMP AX (или другой подходящий регистр), а на Z80: JP (HL) (ил IX или IY). На этих процессорах NEXT состоит из двух операций, которые на 6809 выглядят так:

NEXT: LDX ,Y++ ; (IP) -> W, и IP+2 -> IP
JMP ,X ; JMP (W)

(На i8086 это может быть сделано с помощью LODSW , JMP AX). Результат описанного изображен на рисунке 4 как "case 1". Адрес поля кода слова DEUX извлекается из содержимого высокоуровневого слова, и IP увеличивается. Затем, вместо извлечения, производится переход на адрес поля кода (то есть процессор переходит прямо на адрес поля кода слова). CFA остается в регистре W, аналогично предыдущему примеру с косвенным ШК. С момента появления этого адреса в регистре мы может просто разместить JMP на DOCON в поле кода, и этот фрагмент кода будет работать так же, как раньше.

Изображение

Иногда, некоторые процессоры (к примеру 6809 и PDP-11) позволяют делать NEXT для прямого ШК размером в одну машинную инструкцию:

NEXT: JMP [,Y++] ; (IP) -> temp, IP+2 -> IP, JMP (temp)

Это так же заставит процессор перейти на поле кода слова DEUX. Но имеется одно большое различие: CFA не сохраняется ни в одном из регистров! Так как же фрагмент машинного кода найдет адрес поля параметров? За счет размещения вызова (CALL или JSR) в поле кода вместо прямого перехода (JMP). На большинстве процессоров инструкция CALL заталкивает адрес возврата на стек – адрес следующий за этой командой – на аппаратный стек возвратов. Как показано на Рис.4 “case2” этот адрес точно соответствует началу нужного нам поля параметров! Поэтому, DOCON должен извлечь этот адрес со стека возвратов (балансируя JSR вызов в поле кода), и затем использовать этот адрес для извлечения константы, находящейся в поле параметров. Таким образом:

DOCON: PULS X ; вытолкнуть PFA с аппаратного стека возвратов
LDD ,X ; извлечь ячейку из поля параметров
PSHU D ; затолкнуть содержимое в стек данных
NEXT ; передать управление следующему слову

Сравните это с версией для косвенного ШК. Одна инструкиця добавлена к DOCON, но одна инструкция удалена из NEXT. DOVAR и ENTER так же на одну инструкцию длиннее:

DOVAR: PULS X ; вытолкнуть адрес поля параметров с аппаратного стека
PSHU X ; поместить этот адрес на стек данных
NEXT

ENTER: PULS X ; вытолкнуть PFA слова
PSHS Y ; сохранить старый IP
TFR X,Y ; PFA становится новым IP
NEXT

Теперь давайте вернемся к концу предыдущей статьи, и перечитаем мои «Оппа», для того, чтобы заметить почему мы не можем просто перечитывать CFA используя IP. Так же отметьте различие между соглашением по расположению указателей Форт-стеков в 6809 процессора в регистрах U и S.

Подпрограммный ШК
Подпрограммный ШК подобно прямому передает управление полю кода Форт-слова напрямую. Только сейчас в слове не нужен NEXT, не нужен виртуальный IP, не нужен регистр W. Поэтому не остается другого выхода кроме использования JSR (CALL) в поле кода – в данном случае это единственный вариант получения адреса поля параметров исполняемого слова. Этот процесс иллюстрируется на Рис.5

Изображение

«Высокоуровневый код» - это серия подпрограммных вызовов (CALL-ов) исполняемых процессором непосредственно. Когда исполняется JSR DEUX, адрес следующей команды в определении заталкивается в аппаратный стек возвратов. Затем исполняется JSR DOCOLON внутри слова DEUX, что приводит к заталкиванию другого адреса возвратов (адрес поля параметров слова DEUX) на вершину стека возвратов. DOCOLON может вытолкнуть этот адрес со стека возвратов и использовать для извлечения константы, а затем вернуться по сохраненному в стеке возвратов адресу, то есть JSR + :

DOCON: PULS X ; извлечь адрес поля параметров со стека возвратов
LDD ,X ; извлечь ячейку данных из поля параметров слова
PSHU D ; вытолкнуть значение в стек данных
RTS ; перейти на следующее определение

Мы продолжаем обсуждать поле кода и поле параметров в подпрограммном ШК. В каждом «типе» Форт слов (исключая CODE и двоеточные определения) поле кода – это пространство, занимаемое инструкцией JSR или CALL (аналогично прямому ШК), и поле параметров, следующим за ней. Таким образом, на 6809 поле параметров будет находиться по адресу CFA+3. Смысловое значение термина «поле параметров» становится чем-то туманным в низкоуровневых CODE словах и двоеточных определениях, это станет ясно в следующих статьях.

Особый случай: низкоуровневые определения.

Из приведенных ранее обобщений есть существенное исключение. Это низкоуровневые (или CODE) определения – Форт-слова, которые определены как подпрограммы в машинном коде. Эта прекрасная особенность тривиально просто реализуется в Форте,
This wonderful capability is trivially easy to implement in Forth, так как каждое Форт-слово исполняет какой-либо кусок машинного кода.

Машинный код низкоуровневого слова всегда содержится в поле данных Форт-слова. Поле кода в системах с косвенным ШК должно содержать адрес исполняемого машинного кода. Поэтому машинный код размещается в поле параметров, а поле кода содержит адрес начала поля параметров, как показано на Рис.6.

Изображение

В прямом ШК и подпрограммном, мы можем (по аналогии) размесить в поле кода JUMP на поле параметров. Но это будет бессмысленно, так как поле параметров следует сразу же за полем кода Форт-слова! Поле кода может быть заполнено NOP-ами для достижения аналогичного результата. Но еще лучше начать размещать машинный код в поле кода и продлить его в поле параметров. В этом случае поле кода и поле параметров перестают различаться. Это не проблема, так как мы не нуждаемся в этом различении в низкоуровневых словах (Это имеет значение для декомпиляторов и различных программных трюков, которые мы в данной работе не будем обсуждать).

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

Использование ;CODE
Три вопроса остаются без ответа:

a. Как мы должны создавать новые Форт-слова, которые содержат некоторые произвольные данные в поле параметров?

b. Как мы будем изменять поле кода этого слова, чтобы указать на некоторый используемый нами машинный код?

c. Как мы будем компилировать (ассемблировать) этот фрагмент машинного кода, который изолирован от использующие его слов?

Ответ на пункт «а»: мы пишем Форт-слова для реализации этого. В Форте для этого существуют так называемые «определяющие слова», которые во время исполнения могут создавать другие слова. CONSTANT - один из примеров определяющих слов. Всю работу определяющего слова выполняет слово ядра CREATE , которое берет из входного потока имя слова, создает заголовок слова и поле кода для нового слова, и связывает его в текущий словарь (в fig-forth это слово называется <BUILDS). Программисту остается создать поле данных.

Ответ на «б» и «с» воплощен в два слова, называемые (;CODE) и ;CODE соответственно. Для того, чтобы понять как они работают, давайте глянем на определяющее слово CONSTANT теперь написанное на Форте. Используем оригинальный пример для системы с косвенным ШК 6809 :

: CONSTANT ( n -- )
CREATE \ создать новое слово
, \ компилировать верхнее значение со стека
\ как первый ( и единственный) параметр поля данных
;CODE \ завершить высокоуровневый код и начать низкоуровневый
LDD 2,X \ фрагмент машинного кода для DOCOLON
PSHU D \ " " " "
NEXT \ " " " "
END-CODE

В примере Форт слово состоит из двух частей. Все от : CONSTANT до ;CODE – высокоуровневый Форт-код, исполняемый при вызове слова CONSTANT. Все от ;CODE до END-CODE – это машинный код, исполняемый, когда «потомок» слова CONSTANT (слово CONSTANT класса), такой как UN и DEUX исполняется. То есть, все начиная с ;CODE до END-CODE – это фрагмент машинного кода, на который будут указывать все слова константы, определенные чезез CONSTANT. Имя ;CODE означает что высокоуровневая часть слова закончилась (“;”) и начинается определение в машинном коде (“CODE”). В любом случае это не означает, что в словаре будет создано два отдельных имени {*но вполне может так быть}. Все, начиная с “: CONSTANT” до “END-CODE”, содержится в поле параметров слова CONSTANT см. на Рис. 7.

Изображение

Дерик и Бакер [DER82] назвали три «последовательности» которые позволяют понять работу определяющих слов:

Первая последовательность, когда слово CONSTANT определяется. Это включает и высокоуровневую часть определения и ассемблерную, то есть момент включения слова CONSTANT (изображенного на Рис.7) в словарь. Как мы дальше увидим, ;CODE – директива компилятора, исполняемая во время определения первой последовательности.

Последовательность 2, это когда слово CONSTANT исполняется, то есть когда создается слово константного типа. В примере

2 CONSTANT DEUX

последовательность 2 начинается во время исполнения слова CONASTANT , и слово DEUX добавляется в словарь (Рис.7). Во время второй последовательности выскоуровневая часть CONSTANT исполняется, в том числе слово (;CODE).

Третья последовательность исполняется во время исполнения определенного с помощью CONSTANT слова. В нашем примере, третья последовательность выполняется, когда DEUX исполняется чтобы вытолкнуть значение 2 на стек данных. То есть это время исполнения части машинного кода слова CONSTANT.

Слова ;CODE и (;CODE) делают следующее:

;CODE исполняется во время первой последовательности, то есть во время сборки CONSTANT. Это пример Форт-слова немедленного исполнения – слово исполняется во время компиляции Форт-кода. ;CODE делает три вещи:

a. компилирует в код определяемого CONSTANT слово (;CODE)
b. выключает режим компиляции, и
c. запускает Форт-ассемблер.

(;CODE) – это часть слова CONSTANT, поэтому оно исполняется во время исполнения слова CONSTANT (вторая последовательность). Оно выполняет следующие действия:

a. Оно возвращает адрес машинного кода, который следует сразу за ним. Это выполняется за счет снятия адреса со стека возвратов.

b. Оно выкладывает этот адрес в поле кода только что определенного слова (с помощью CREATE). Форт-слово LAST (иногда LATEST) возвращает адрес этого слова.

c. выполняет действие слова EXIT (так же называемого ;S) так, чтобы адресный интерпретатор не пытался выполнить этот машинный код. Это высокоуровневый «выход из подпрограммы», который завершает Форт-определение.

В рамках стандарта 83 года [LAX84] это типично выглядит так:
: ;CODE
COMPILE (;CODE) \ компилировать код (;CODE) в определение
?CSP [COMPILE] [ \ выключить режим компиляции
REVEAL \ выполняет действие, аналогичное ‘;’
ASSEMBLER \ включает ассемблер
; IMMEDIATE \ Это слово немедленного исполнения!

: (;CODE)
R> \ выталкивает адрес машинного кода со стека возвратов
LAST @ NAME> \ берет CFA последнего слова
! \ сохраняет адрес кода в поле кода создаваемого слова
;

(;CODE) наиболее необычный из них.
С момента исполнения высокоуровневого Форт-определения следующий адрес в теле CONSTANT (высокоуровневый адрес возврата) заталкивается на стек возвратов. Поэтому, выталкивание из стека возвратов изнутри (;CODE) приведет к получению адреса, следующего за машинным кодом. Так же, выталкивание этого значения из стека возвратов будет «пропускать» один уровень подпрограммного выхода, таким же образом как при (;CODE) выходе, это будет выход в слово вызывающее CONSTANT. Что эквивалентно возврату в CONSTANT и затем сразу выходу из CONSTANT. Используйте Рис.7 и проследите исполнение слов CONSTANT и (;CODE) для того, чтобы разобраться в их работе.

Прямой и подпрограммный ШК

Для прямого и подпрограммного ШК действие слов ;CODE (;CODE) идентично косвенному: вместо хранения адреса в поле кода хранится машинная инструкция JUMP или CALL. Для абсолютных JUMP или CALL, вероятно, единственное отличие в том, что адрес может храниться в конце поля кода, как операнд операции JUMP (или CALL). В случае 6809 адрес будет сохранен в последних двух байтах трехбайтовой команд JSR. Однако, некоторые Форты, к примеру, Pygmy на i8086, используют относительное ветвление в поле кода. В этом случае должно быть рассчитано относительное смещение и вставлено в операцию ветвления.

Высокоуровневые решения на Форте.

Мы уже рассмотрели, как заставить Форт-слово исполнить выбранный фрагмент машинного кода и как передать этому фрагменту кода адрес поля параметров слова. Но как можно написать «подпрограмму действия» на высокоуровневом Форте?

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

a. как найти адрес высокоуровневого кода, ассоциируемого с этим Форт-словом?

b. как мы будем (из машинного кода) вызывать Форт-интерпретатор для высокоуровневой подпрограммы действия?

c. Как мы будем передавать этой подпрограмма адрес поля параметров для исполняемого слова?

Ответ на (с) – как передавать аргумент в высокоуровневое Форт-определение – прост. На стеке параметров, конечно же. Наша машинная подпрограмма должна заталкивать адрес поля параметров на стек перед тем, как вызвать высокоуровневый код (из нашей предыдущей работы мы знаем, как подпрограмма в машинном коде может получить адрес поля параметров).

Ответ на (b) несколько сложнее. Обычно, мы хотим делать что-то похожее на Форт-слово EXECUTE, которое вызывает Форт-слово или, возможно, ENTER, который вызывает двоеточное определение. Оба близки к ключевым словам ядра. DODOES будет иметь с ними сходство.

Вопрос (a) самый мудреный. Куда поместить адрес высокоуровневой подпрограммы? Вспомните, поле кода не указывает на высокоуровневый код, оно должно указывать на машинный код. Два подхода использовались ранее:

1. Fig-Forth решение. Fig-Forth резервирует первую ячейку в поле параметров для хранения адреса высокоуровневого кода. DODOES подпрограмма в последствии извлекает адрес поля параметров, выталкивает адрес реальных данных (обычно PFA+2) на стек данных, извлекает адрес высокоуровневой подпрограммы и исполняет ее.

С этим решением связаны две проблемы. Во-первых, структура поля параметров различна в низкоуровневых и высокоуровневых словах. К примеру, CONSTANT , будучи определено в машинном коде, будет хранить свои данные в PFA, в то время, как оно же определенное на высокоуровневом коде будет хранить свои данные, обычно, по адресу равному PFA+2.

Второе, каждое объявление высокоуровневого действия приводит к дополнительному расходу одной ячейки памяти. То есть, если CONSTANT использует высокоуровневое действие, каждая вновь созданная в программе константа будет на одну ячейку!

К счастью, хорошие Форт-программисты быстро изобрели решение, которое побороло эти проблемы, и решение fig-Forth было забыто.

2. Современное решение. Большинство Фортов сегодня объединяет различные фрагменты машинного кода с каждой высокоуровневой процедурой действия. Поэтому, высокоуровневые константы будут иметь собственное поле кода, указывающее на фрагмент машинного кода, чья функция указывает на «инициализирующую» подпрограмму для высокоуровневого действия VARIABLE, и т.п.

Чрезмерное ли это повторение кода? Нет, потому что каждый такой фрагмент машинного кода лишь подпрограммный вызов на обычную стартовую подпрограмму DODOES (Это отлично от fig-Forth подпрограммы DODOES). Адрес высокоуровневого кода в DODOES передается как параметр «инлайн» подпрограммы. То есть, адрес высокоуровневого кода кладется сразу после JSR/CALL инструкции. DODOES может затем вытолкнуть адрес с процессорного стека и произвести чтение, чтобы получить этот адрес.

Фактически, мы сделали более двух упрощений. Высокоуровневый код расположен сразу за инструкцией JSR/CALL. DODOES извлекает адрес прямо с процессорного стека. И как только мы знаем, что это высокоуровневый Форт-код, мы работаем с его полем кода и просто компилируем высокоуровневый код... по-существу, встраивая действие ENTER в DODOES.

Теперь, каждое «определенное» слово указывает на кусочек машинного кода… в его поле параметров место не расходуется. Этот кусочек машинного кода – JSR или CALL инструкция, за которой расположен высокоуровневый код. В 6809 примере мы занимали два байта в каждой константе под трехбайтовый JSR, который появляется лишь однажды.

Это, несомненно, наиболее закрученная программная логика во всем ядре Форта! Так давайте посмотрим, как это реализуется на практике, используя косвенный шитый код для 6809 процессора.

Изображение

Рисунок 8 показывает высокоуровневую реализацию константы DEUX. Когда адресный интерпретатор встречает DEUX (то есть, когда Форт IP(1)) он выполняет обычную вещь: извлекает адрес, хранящийся в поле кода DEUX, и передает на него управление. По этому адресу находится инструкция JSR DODOES, поэтому второй переход (в этот раз подпрограммный вызов) производится сразу. DODOES затем должен произвести следующие действия:

a. Затолкнуть адрес поля параметров слова DEUX на стек данных для последующего использования в высокоуровневой подпрограмме. Пока JSR инструкция не затрагивает никаких регистров, мы ожидаем обнаруживать адрес поля параметров слова DEUX в регистре W.

b. Добыть адрес высокоуровневой подпрограммы, за счет выталкивания из процессорного стека (Напомню, что выталкивание адреса со стека процессора вернет адрес следующий сразу за вызывающей JSR командой). Это высокоуровневый код, то есть поле параметров двоеточного определения.

c. сохранить старое значение указателя интерпретации (IP(2)) на стеке возвратов, с этого момента регистр IP будет использоваться при исполнении высокоуровневого фрагмента кода. По существу, DODOES должен использовать IP, подобно тому, как это делает ENTER. Помните, что стек возвратов может не совпадать с процессорным стеком.

d. положить адрес высокоуровневого слова в IP (это IP(3) на рисунке 8).

e. выполнить NEXT для продолжения интерпретации выскоуровневого кода с нового места.

Возьмем косвенный шитый код 6809, и следующее:

* W не автоинкрементируется с помощью NEXT (т.е. W будет содержать адрес поля кода слова, в которое вошел NEXT).

* на 6809 S - это PSP, и U - это RSP (то есть, стек процессора не является стеком возвратов)

* на 6809 Y – это IP, и X – это W.

Пересмотрим код NEXT для этих условий:

NEXT: LDX ,Y++ ; (IP) -> W, и IP+2 -> IP
JMP [,X] ; (W) -> temp, JMP (temp)

DODOES может быть написан следующим образом:

DODOES: LEAX 2,X ; устанавливаем W на поле параметров
PSHU Y ; (c) заталкиваем старый IP на стек возвратов
PULS Y ; (b,d) извлекаем новый IP со стека процессора
PSHS X ; (a) заталкиваем W (адрес поля параметров)
; на стек данных
NEXT ; (e) вызываем адресный интерпретатор

Эти операции несколько не последовательны. До тех пор, пока правильные данные уходят на правильные стеки (или в правильные регистры) в правильное время, точная последовательность операций не критична. В этом случае мы учитываем факт, что старое значение IP может быть затолкнуто на стек возвратов перед извлечением нового IP из стека процессора.

На некоторых процессорах аппаратный стек используется как стек возвратов. В этом случае один шаг требует привлечения временного хранилища. Если мы выбрали S=RSP и U=PSP выше, DODOES должен быть:

DODOES: LEAX 2,X ; устанавливаем W на поле параметров
PSHU X ; (a) выталкиваем W на стек данных
PULS X ; (b) выталкиваем адрес с процессорного стека
PSHS Y ; (c) заталкиваем старый IP в стек возвратов
TFR X,Y ; (d) кладем адрес следующего токена в IP
NEXT ; (e) вызываем адресный интерпретатор

С момента, как мы обмениваем вершину стека возвратов/процессорного с IP, мы должны использовать X как временный регистр. Таким образом, мы должны сохранить адрес поля кода (шаг «а») перед использованием регистра X.

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

Прямой шитый код

Логика DODOES аналогична таковой в Фортах с прямым шитым кодом. Но реализация может быть различна, в зависимости от того используется ли JMP или CALL в поле кода слов.

a. JMP в поле кода. Форт с прямым ШК может использовать JMP в поле кода, если адрес исполняемого слова находится в регистре. Это, скорее всего будет адрес поля кода.

С точки зрения DODOES это идентично косвенному ШК. В нашем примере DODOES видит, что адресный интерпретатор перешел на машинный код, ассоциируемый с DEUX, и этот код JSR на DODOES. Не имеет значения, что первый JUMP теперь прямой переход, а не косвенный, содержимое регистров и стека аналогичны. Поэтому код для DODOES будет идентичное таковому для косвенного ШК (конечно же NEXT другой, и W может иметь отличное смещение, чтобы попасть в поле параметров).

b. CALL/JSR в поле кода. В прямом ШК 6809 мы не можем просто получить адрес поля кода исполняемого слова, поэтому Форт-слово должно содержать JSR в поле кода. Вместо нахождения адреса поля параметров Форт-слова в регистре, мы обнаружим его на процессорном стеке.

Изображение

DEUX пример для этого случая отображен на рисунке 9. В момент IP(1), Форт интерпретатор прыгает на фрагмент машинного кода слова DEUX (и увеличивает IP). В поле кода находится JSR на фрагмент машинного коды DEUX. По этому адресу находится второй JSR на DODOES. Таким образом, два адреса будет положено на стек процессора. Адрес возврата от первого JSR – это адрес поля параметров слова DEUX. Адрес возврата второго JSR (верхний элемент на процессорном стеке) – это адрес высокоуровневого кода, который должен быть исполнен. DODOES должен гарантировано, что старый IP сохранен на стек возвратов, адрес поля параметров слова DEUX положен на стек данных, и адрес высокоуровневого кода загружен в IP. Решение очень чувствительно к распределению регистров. Для S=PSP (процессорный стек) и U=RSP, код для NEXT и DODOES будет:

NEXT: LDX [,Y++] ; (IP) -> temp, IP+2 -> IP, JMP (temp)

DODOES: PSHU Y ; затолкнуть старый IP на стек возвратов
PULS Y ; вытолкнуть новый IP с процессорного стека
; заметка: процессорный стек теперь – это стек данных,
; и верхний элемент теперь адрес поля параметров слова -
; именно то, что мы хотим!
NEXT ; вызвать адресный интерпретатор

Проверьте для себя, что прохождение через NEXT, DEUX и DODOES заталкивает в итоге одно значение (адрес поля параметров слова DEUX) на стек данных!

Подпрограммный ШК

На Фортах с подпрограммным ШК не нужны ни IP ни W регистры, и высокоуровневый код – это чистый машинный код (серия подпрограммных вызовов). Единственное различие между выскоуровневым действием и ;CODE действием заключается в том, что адрес поля параметров должен быть вытолкнут на стек данных. «определенные» слова содержат команду CALL/JSR в поле кода, и процессорный стек используется как стек возвратов Форта, поэтому DODOES в основном занимается стековыми манипуляциями.

Изображение

Рисунок 10 показывает пример 6809 подпрограммного ШК для слова DEUX с высокоуровневым определением. Во время выполнения DODOES три значения заталкиваются на процессорный/возвратов стек: адрес возврата в «главное» слово, адрес поля параметров DEUX, и адрес кода высокоуровневой программы DEUX. DODOES должен вытолкнуть два последних, затолкнуть PFA на стек данных, и перейти на код действия:

DODOES: PULS X,Y ; адрес кода действия -> X, PFA -> Y
PSHU Y ; затолкнуть PFA на стек данных
JMP ,X ; перейти на код действия

DODOES для 6809 теперь подпрограмма, состоящая всего из трех инструкций. Она может быть в дальнейшем укорочена за счет «заинлайнивания» DODOES, т.е. замены JSR DODOES на эквивалентный машинный код. В итоге получается меньше на один JSR, это упрощает стековые манипуляции до:

PULS X ; вытолкнуть адрес поля параметров со стека процессора
PSHU X ; и поместить его на стек данных
...высокоуровневый код DEUX...

Это заменяет трехбайтовый JSR на четыре байта приведенного кода с приличным выигрышем в скорости исполнения. Для 6809 это, вероятно, хороший выбор. Для процессоров, подобных 8051, DODOES лучше подойдет подпрограмма.

Использование DOES>

Мы изучили, как создавать новое Форт-слово с помощью ;CODE, хранящее произвольные данные в поле параметров, и как менять указатель в поле кода на новый фрагмент машинного кода. Как можно компилировать высокоуровневые слова, и делать так, чтобы новое слово ссылалось на него?

Ответ содержится в двух словах DOES> и (DOES>), которые являются высокоуровневым эквивалентом слов ;CODE и (;CODE). Чтобы их понять, давайте посмотрим на пример их использования:

: CONSTANT ( n -- )
CREATE \ создать новое слово
, \ добавить значение с вершины стека данных
\ в текущее определение как первое значение в
\ поле параметров созданного слова
DOES> \ завершение «создающей» части, начало части «действия»
@ \ прочесть значение из поля параметров слова
;

Сравните это с предыдущим примером ;CODE и заметьте, что DOES> выполняет функцию, аналогичную ;CODE. Все от CONSTANT до DOES> исполняется когда слово CONSTANT вызывается. Это код, который формирует поле параметров определяемого слова. Все от DOES> до ; - высокоуровневый код, исполняемы когда «потомок» CONSTANT (к примеру, DEUX) вызывается, т.е. высокоуровневый фрагмент кода, на который указывает поле кода (мы увидим, что JSR DODOES включено перед этим высокоуровневым фрагментом). Так как с ;CODE оба класса: порождающий и действия содержатся внутри тела Форт-слова CONSTANT, как показано на рисунке 11.

Изображение

Пересмотрите последовательности 1, 2, и 3. Слова DOES> и (DOES>) делают следующее:

DOES> исполняется в первую очередь, когда компилируется CONSTANT. Таким образом DOES> - это Форт слово немедленного исполнения, оно делает следующие две вещи:

a. компилирует Форт слово (DOES>) в CONSTANT.
b. компилирует JSR DODOES в CONSTANT.

Замечу, что DOES> оставляет Форт-компилятор включенным, для последующей компиляции высокоуровневого фрагмента, следующего за ним. Так же, даже если JSR DODOES не является Форт-кодом, слова немедленного исполнения, такие как DOES> могут компилироваться в середину Форт-определения.

(DOES>) является частью слова CONSTANT, поэтому она исполняется, когда CONSTANT исполняется (последовательность 2). Происходит следующее:

a. получается адрес машинного кода, который следует сразу за JSR DODOES, с помощью выталкивания IP со стека возвратов.

б. этот адрес записывается в поле кода только что определенного с помощью CREATE слова.

c. выполняется действие EXIT, заставляющего CONSTANT завершить выполнение не допуская исполнения следующего фрагмента кода (который выполняется в момент вызова созданной константы).

Действие (DOES>) идентично (;CODE)! Отдельное слово не обязательно. F83, к примеру, использует код (;CODE) в обоих случаях: ;CODE и DOES>. Я буду использовать (;CODE) с этого момента вместо (DOES>).

Вы уже видели работу слова (;CODE). Определение DOES>:

: DOES>
COMPILE (;CODE) \ компилирует (;CODE) в определение
0E8 C, \ байт опкода CALL
DODOES HERE 2+ - , \ относительное смещение к DODOES
; IMMEDIATE

где DODOES константа, которая хранит адрес подпрограммы DODOES (реальный код F83 несколько отличается, согласно требованиям метакомпилятора F83). DOES> не требуется играть с CSP, или флагом smudge, с момента выключения Форт-компилятора. В случае 8086 инструкция CALL ожидает относительный адрес… отсюда арифметика использующая DODOES и HERE. На 6809 DOES> будет выглядеть примерно так:
: DOES>
COMPILE (;CODE) \ компилирует (;CODE) в определение
0BD C, \ байт опкода операции JSR
DODOES , \ адресный операнд: адрес DODOES
; IMMEDIATE

Тут вы можете увидеть как машинный JSR DODOES компилируется после высокоуровневого (;CODE) и перед высокоуровневым кодом «действия».

Прямой и подпрограммный ШК.

Единственное различие между прямым и подпрограммным ШК заключается в том, как поле кода передает управления на другое слово. Это делается с помощью (;CODE), и требуемые изменения уже разобраны. DOES> не меняется вообще, только если вы не используете подпрограммный ШК и не разворачиваете JSR DODOES в машинный код. В этом случае DOES> модифицируется в «инлайн» машинный код вместо подпрограммного вызова DODOES.

Вперед и вверх

Кто мог подумать, что несколько линий кода потребуют такого количества пояснений?
Именно поэтому я восхищаюсь ;CODE и DOES> так сильно… Я никогда ранее не видел таких запутанных, мощных и гибких конструкций, закодированных с подобной экономией.

В следующей статье я обсужу достоинства ассеблеров против метакомпиляторов, и предоставлю реальные CODE слова для нашей Форт обучающей системы.

Литература:


[DER82] Derick, Mitch and Baker, Linda, Forth Encyclopedia, Mountain View Press (1982). A word-by-word description of fig- Forth in minute detail. Still available from the Forth Interest Group, P.O. Box 2154, Oakland CA 94621.

[LAX84] Laxen, H. and Perry, M., F83 for the IBM PC, version 2.1.0 (1984). Distributed by the authors, available from the Forth Interest Group or GEnie.



Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
 Заголовок сообщения:
СообщениеДобавлено: Вт ноя 10, 2009 20:50 
Не в сети
Аватара пользователя

Зарегистрирован: Вт фев 17, 2009 19:58
Сообщения: 112
Откуда: Барнаул
Благодарил (а): 21 раз.
Поблагодарили: 16 раз.
Хорошо, что есть кому переводить. Большое спасибо. А можно потом оформить перевод в виде отдельного файла, или хотя бы разместить целиком (а не в виде набора топиков форума,) в интернете. Всё вместе читать удобнее.


Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
 Заголовок сообщения:
СообщениеДобавлено: Вт ноя 10, 2009 20:55 
Не в сети
Moderator
Moderator
Аватара пользователя

Зарегистрирован: Чт май 04, 2006 00:53
Сообщения: 5062
Откуда: был Крым, теперь Новосибирск
Благодарил (а): 23 раз.
Поблагодарили: 63 раз.
ну, собственно, можете сами это скинуть в отдельный файл (я не буду против).
тут хотелось бы иметь возможность обсуждать содержимое статьи (иногда обсуждение ценней содержимого статьи),
поэтому и родилась идея этого раздела форума.

_________________
Мне бы только мой крошечный вклад внести,
За короткую жизнь сплести
Хотя бы ниточку шёлка.
fleur


Вернуться к началу
 Профиль Отправить личное сообщение  
Ответить с цитатой  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 3 ] 

Часовой пояс: UTC + 3 часа [ Летнее время ]


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 14


Вы не можете начинать темы
Вы можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
phpBB сборка от FladeX // Русская поддержка phpBB