ОСОБЕННОСТИ РЕАЛИЗАЦИИ КОНСТРУКЦИИ CREATE DOES>. 1. ВСТУПЛЕНИЕ Разрабатывая свою "домашнюю" Форт-машину я столкнулся с необычным явлением (в Форте вообще мало обычного) - конструкцией CREATE/DOES>. Поиски детального объяснения работы этой конструк- ции (на примитивном уровне) привели к появлению этой статьи. Надеюсь она поможет тем, кто испытывает аналогичные затруднения. В статье используется ассемблер семейства x86 в диалекте NASM'а. Также следует отметить, что в своей Форт - машине я использовал Subroutine Threaded Code (STC) with inline expansion для микропроцессора Intel 80386 (это влияет на способ генерации и тип генерируемого кода). Также хочу отметить,что мои знания в Форте носят началь- ный уровень - и терминология может не совпадать с общепринятой. 2. СОЗДАНИЕ ФОРТ-СЛОВА С ПОМОЩЬЮ CREATE Приступая к исследованию необходимо обратиться к первоисточнику- ANSI X3.215-1994 ( далее приводиться перевод Сергея Кадочникова 2:4657/33.3): ---------------------------------------------------------------- 6.1.1000 CREATE CORE ( "name" -- ) Пропускает ведущие разделители пробелы. Выделяет name, ограни- ченное пробелом. Создает определение для name с семантикой вы- полнения , определенной ниже. Если указатель области данных не выровнен , резервирует достаточно области данных для его вырав- нивания. Новый указатель области данных определяет поле данных name. CREATE не распределяет область данных в поле данных name. name Выполнение: ( -- a-addr ) a-addr - адрес поля данных name. Семантика выполнения name может быть расширена использованием DOES>. См.: 3.3.3 Область данных, 6.1.1250 DOES>. ---------------------------------------------------------------- Ну что же, достаточно туманно. Для начала - слова CREATE и DOES> не являются словами немедленного исполнения (т.е. не IMMEDIATE). А теперь попытаемся вчитаться в текст. Слово CREATE должно выполнять следующие шаги: 1) Получить следующие слово из входного потока. 2) Создать слово в словаре. 3) Созданному слову добавить функциональность которая будет записывать в стек адрес следующей свободной ячейки памяти (после выполнения CREATE). Пункты 1-2 более-менее понятны, а что же за код упоминается в 3? Например, это может быть следующий код: POP EDX PUSH DWORD FREE_MEM ;занесение offset FREE_MEM в стек PUSH EDX RET FREE_MEM: Пояснения: 1) Добавляемый код-это не ссылка на подпрограмму- каждому новому слову наново добавляется указанный код. 2) Соответственно смещение метки FREE_MEM - каждый раз разное. 3) В приведенном примере эмулируется стек возвратов через конст- рукцию POP EDX/PUSH EDX Скажем,можно избавиться от пункта 2,применив захардкодив следую- щее: CALL .a1 ; думаю, знакомая конструкция ;) .a1: POP EAX ; в EAX - смещение .a1 ADD EAX, .a2-.a1 ; прибавляем дельту POP EDX PUSH EAX PUSH EDX RET .a2: Выполнив данные требования мы получим , полностью совместимое слово CREATE. Например уже будет работать конструкция вида: CREATE MASSIV 10 CELLS ALLOT После выполнения слова MASSIV на вершине стека окажется адрес 10 выделенных ячеек (Форт-ячеек) памяти 3. МОДИФИКАЦИЯ ПОВЕДЕНИЯ СЛОВА - DOES> Ну что же - с CREATE мы все выяснили теперь давайте рассмотрим DOES>. ---------------------------------------------------------------- 6.1.1250 DOES> CORE Интерпретация: Семантика интерпретации для этого слова не определена. Компиляция: ( C: colon-sys1 -- colon-sys2 ) Добавляет семантику времени-выполнения ниже к текущему определе- нию. В любом случае текущее определение представленное находимым в словаре при компиляции DOES> - определенное реализацией. Потребляет colon-sys1 и производит colon-sys2.Добавляет семанти- ку инициирования, данную ниже к текущему определению. Время-выполнения: ( -- ) ( R: nest-sys1 -- ) Заменяет семантику выполнения самого последнего определения,упо- минаемого как name , семантикой выполнения имени данной ниже. Возвращает управление на вызывающее определение, определенное nest-sys1. Неопределенная ситуация существует если name не было определено CREATE, или определенным пользователем словом которое вызывает CREATE. Инициирование: ( i*x -- i*x a-addr ) ( R: -- nest-sys2 ) Сохраняет зависящую-от-реализации информацию nest-sys2 о вызы- вающем определении. Размещает адрес поля данных name на стеке. Состояние стека i*x представляет параметры name. name Выполнение: ( i*x -- j*x ) Выполняет часть определения , которая начинается с семантики инициирования добавленной изменившим name DOES>. Состояния стека i*x и j*x представляют параметры, и результаты name , соответст- венно. См.: 6.1.1000 CREATE. ---------------------------------------------------------------- Слово DOES> должно выполнять: 1) Последнему созданному слову добавить следующую функциональ- ность. 2) Выполнить операцию RET из вызвавшей его слова-подпрограммы. Что это значит? Давайте рассмотрим действия по пунктам: 1) Модифицировать код уже сформированного слова плохо - мы можем получить проблемы при переносе Форт-машины на другую платфор- му. Поэтому создадим новый код. Делать он будет следующее: а) Сгенерировать код эмулирующий вход в подпрограмму ( все- таки архитектура x86 содержит один стек). б) сделать CALL на "старый" код слова (т.е. получим на верши- ну стека адрес выделенной памяти в) сделать jmp на следующую инструкцию определяющего слова (этот адрес лежит в стеке возвратов). После этого необходимо ассоциировать новый код с созданным словом. Все. 2) Тут все просто - надо сделать DROP из стека возвратов один адрес. И выйти. Это гарантирует что код, следующий за DOES> не выполниться. Дотошный читатель может задать вопрос: "а что же будет в дейст- вительности происходить при вызове модифицированного слова ?". Давайте посмотрим: 1) Отработает код входа в слово (эмулирующий стек возвратов). 2) CALL на старый код (получаем выделенную память). 3) JMP внутрь определяющего слова 4) Выполнение инструкций определяющего слова. 5) Выход из определяющего слова. Фактически - выход из пункта 1- все-таки мы делали jmp в пункте 3. Теперь получится что заработает конструкция вида: : CONSTANT CREATE , DOES> @ ; Но это еще не все. Как "ассоциировать" новый код ? Ведь в угоду переносимости,мы великодушно отказались от модификации уже сфор- мированного байт-кода. Для этого необходимо посмотреть на струк- туру словарной статьи. Как правило вхождение описывают макросом: %MACRO FRT 4 %2_lfa: dd %1_lfa ; предыдущий lfa %2_nfa: db %3,%4 %2: %ENDM После чего используют этот макрос в виде: FRT ??? , FRT_EMIT, 4, 'EMIT' POP EDX POP EAX PUSH EDX MOV DL,AL MOV AH,2 INT 21h RET FRT FRT_EMIT, FRT_KEY, 3, 'KEY' MOV AH,01h INT 21H XOR ECX,ECX MOV CL,AL POP EDX PUSH ECX PUSH EDX RET Как видите, лучше завести в словарной статье поле %2_cfa, кото- рое будет содержать адрес машинных команд слова. Тогда мы без трудностей ( связанных с модификацией кода созданного CREATE ) можем изменить функциональность нашим DOES> . Макрос FRT в моей версии: %MACRO FRT 4 %2_lfa: dd %1_lfa ; предыдущий lfa %2_nfa: db %3,%4 %2_cfa: dd %2 ; смещение кода статьи %2: %ENDM (C) Коменда Вячеслав 2006 v_komenda AT developers.com.ua
© Коменда Вячеслав 1999-2006