В случае, если несколько исключений возникает одновременно, модуль обработки операций с плавающей точкой сигнализирует об одном из них в соответствии с приоритетом, указанным в конце Главы 16. Это означает, например, что SNaN, деленное на 0, дает ошибочную операцию, а не исключение деления на нуль.
Примеры программ обработки исключений
Программы обработки исключений могут иметь различные формы. Они могут изменять правила программирования и арифметики процессора i486. Такие изменения могут переопределить ошибки, установленные по умолчанию, изменить видимость модуля обработки операций с плавающей точкой для программиста или изменить арифметику, определенную для модуля обработки операций с плавающей точкой.
Чтобы изменить результат выдачи исключения, необходимо сначала выполнить ненормальную арифметику или ненормальную загрузку из памяти. Для изменения "видения" модуля обработки операций с плавающей точкой стековой регистр должен быть расширен до размеров памяти, чтобы предоставить для работы "бесконечное" число регистров. Арифметика модуля обработки операций с плавающей точкой может быть автоматически изменена через переопределение точности и диапазона значений при переполнении. Все эти функции процессора i486 могут быть реализованы через числовые прерывания и программы обработки в соответствии с нуждами программиста.
Некоторые другие возможные программы в зависимости от применения могут включать:
Увеличение счетчика особых ситуаций при последней работе с дисплеем или при печати
Печать или выдача на экран диагностических сообщений (например, среда модуля обработки операций с плавающей точкой или регистров)
Прекращение дальнейшего выполнения
Сохранение величины диагностики (NaN) в результате и продолжение вычисления
В зависимости от применения, исключения могут фиксировать, а могут и опускать ошибки. Как только обработчик исключения скорректирует условие, вызвавшее исключение, то при необходимости, команда с плавающей точкой, в которой случилось исключение, может быть выполнена еще раз. Это не может произойти при использовании команды IRET, так как прерывания случаются в команде ESC или в команде WAIT, вызываемой после команды ESC. Обработчик исключений должен получить (используя команду FSAVE или FSTENV) адрес нарушившей команды в программе, которая вызывала команду, сделать копию команды, выполнить ее в контексте нарушенной программы и затем через команду IRET вернуться к потоку текущих команд.
Чтобы откорректировать условие, вызвавшее числовое исключение, обработчик исключений должен распознать состояние модуля обработки операций с плавающей точкой в то время, когда был вызван обработчик исключений. Для восстановления состояния модуля обработки операций с плавающей точкой программисты должны понимать когда, в течение выполнения числовой команды, нарушение было обнаружено.
Недопустимые операции, деление на нуль, ненормализованные исключения обнаруживаются до начала выполнения операции, тогда как переполнение, отрицательное переполнение и исключения нарушения точности не возникают до тех пор, пока не будет вычислен результат. Если исключение обнаружено перед выполнением, то регистровый стек и память модуля обработки операций с плавающей точкой еще не были изменены, так как команда, содержащая нарушение, не выполнялась.
При обнаружении исключения после начала выполнения команды регистровый стек и память выглядят так, будто команды была выполнена; например, они могут быть обновлены. (Однако в командах сохранить и сохранить-извлечь, немаскированное переполнение и отрицательное переполнение обрабатываются подобно исключению перед1 0выполнением; память не обновлялась и из стека ничего не извлекалось.) Примеры в Главе 20 содержат несколько обработчиков исключений для обработки числовых исключительных ситуаций.
Глава 20. Примеры вычислительного программирования
В следующем разделе приведены примеры программ вычислительного характера для процессора i486, написанные на ассемблере ASM386/486. Эти примеры иллюстрируют некоторые полезные приемы для разработки вычислительных программных продуктов и систем на основе процессора i486.
Пример условного ветвления
Как уже обсуждалось в Главе 15, различные числовые команды воздействуют на биты кода условия слова состояния модуля обработки операций с плавающей точкой, хотя существует множество способов для того, чтобы реализовать условное ветвление, следующее за сравнением. Далее приведен основной подход:
Выполнить сравнение.
Сохранить слово состояния. (Слово состояния модуля обработки операций с плавающей точкой можно сохранить прямо в регистре AX.)
Проверить биты кода условия.
Перейти по результату.
На Рисунке 20-1 представлен фрагмент программы, который иллюстрирует, как можно сравнить два находящиеся в памяти вещественные числа двойного формата (подобная программа может быть использована с командой FTST). Числа, которые сравниваются в этом фрагменте, названы A и B. Операция сравнения требует загрузки числа A в вершину регистрового стека модуля обработки операций с плавающей точкой, а затем сравнения его с числом B, выполняя при этом операцию восстановления из стека. После чего слово состояния записывается в регистр AX. A и B имеют четыре возможных порядка, а биты C3, C2 и C0 кода условия указывают, какой из этих порядков установлен. Три бита располагаются в верхнем байте слова состояния модуля обработки операций с плавающей точкой так, чтобы соответствовать флагам нуля, четности и переноса (ZF, PF и CF), когда байт записывается во флаги. Во фрагменте программы флаги ZF, PF и CF регистра EFLAGS устанавливаются в значения битов C3, C2 и C0 слова состояния, а затем используется операция условного перехода для того, чтобы проверить флаги. Результирующая программа очень компактна и записывается всего лишь в семь команд. Команда FXAM обновляет все четыре бита кода условия. На Рисунке 20-2 показано, как таблица переходов может быть использована для того, чтобы определить характер просмотренных величин. Таблица переходов (FXAM_TBL) содержит 16 меток, по одной для каждого возможного кода условия. Обратите внимание, что четыре значения содержат одно и тоже - "EMPTY" (пусто). Первые два значения кода условия соответствуют позиции "EMPTY". Два другие значения в таблице, которые содержат "EMPTY", никогда не будут использованы процессором i486 или математическим сопроцессором 387(TM), но могут быть использованы при выполнении программы на сопроцессоре 80287.
+---------------------------------------------------------------+| . || A DQ ? || B DQ ? || . || . || FLD A ; Загружает A в вершину стека FPU || FCOMP B ; Сравнение A:B, выталкивание A || FSTSW AX ; Сохраняет результат в регистре AX || ; || ; Коды условия содержатся в регистре ЦПУ AX || ; (Результаты сравнения) || ; Загрузка кодов условия во флаги || ; || SAHF || ; || ; Использует условные переходы для того, чтобы || ; определить как упорядочены A и B || ; || JP A_B_UNORDERED ; Тестирование бита C2 (PF) || JB A_LESS ; Тестирование бита C0 (CF) || JE A_EQUAL ; Тестирование бита C3 (ZF) || A_GREATHER: ; C0 (CF) = 0, C3 (ZF) = 0 || . || . || A_EQUAL: ; C0 (CF) = 0, C3 (ZF) = 1 || . || . || A_LESS: ; C0 (CF) = 1, C3 (ZF) = 0 || . || . || A_UNORDERED: ; C2 (PF) = 1 || . |+---------------------------------------------------------------+ Рисунок 20-1. Условное Ветвление при Сравнении
Фрагмент программы выполняет команду FXAM и сохраняет слово состояния. Затем идет манипуляция битами кода условия для окончательного вывода числа в регистр AX, которое равно коду условия, умноженному на 2. При этом включается обнуление неиспользуемых битов в байте, который содержит код, происходит сдвиг бита C3 вправо так, чтобы он был соседним с битом C2, и затем происходит сдвиг кода для того, чтобы умножить его на 2. Полученное значение используется как индекс, по которому выбирается одна из меток из таблицы переходов FXAM TBL (умножение кода условия требуется из-за 2-х байтной длины каждого значения в таблице). Команда безусловного перехода JMP направляет процессор через таблицу переходов к процедуре, которая содержит программу (не показанную в примере) для обработки каждого возможного результата каманды FXAM.
Примеры обработки исключений
Существует множество подходов для написания обработчиков исключительных ситуаций. Один полезный прием - это сделать процедуру обработки исключчений из трех частей: "пролога", "тела" и "эпилога". Эта процедура вызывается через прерывание номер 16.
+---------------------------------------------------------------+| || ; Таблица переходов для процедуры проверки || ; || FXAM_TBL DD POS_UNNORM, POS_NAN, NEG_UNNORM, NEG_NAN, || & POS_NORM, POS_INFINITY, NEG_NORM, || & NEG_INFINITY, POS_ZERO, EMPTY, NEG_ZERO, || & EMPTY, POS_DENORM, EMPTY, NEG_DENORM, EMPTY || . || . || ; Проверить ST и сохранить результат (коды условия) || ; || FXAM || XOR EAX, EAX ; Очистить регистр EAX || FSTSW AX || ; || ; Вычисление смещения в таблице переходов || ; || AND AX, 0100011100000000B ; Очистить все биты || ; кроме C3, C2-C0 || SHR EAX, 6 ; Сдвинуть C2-C0 как (000XXX00) || SAL AH, 5 ; Установить C3 как (00X00000) || OR AL, AH ; Слить C3 и C2-C0 (00XXXX00) || XOR AH, AH ; Удалить старую копию C3 || ; || ; Перейти к процедуре по коду условия || ; || JMP FXAM_TBL[EAX] || ; || ; Метки таблицы перехода, по одной для обработки || ; каждого возможного результата FXAM || ; || POS_UNNORM: || . || POS_NAN: || . || NEG_UNNORM: || . || NEG_NAN: || . || POS_NORM: || . || POS_INFINITY: || . || NEG_NORM: || . || NEG_INFINITY: || . || POS_ZERO: || . || EMPTY: || . || NEG_ZERO: || . || POS_DENORM: || . || NEG_DENORM: || . || |+---------------------------------------------------------------+ Рисунок 20-2. Условное Ветвление при Операции FXAM
При передаче управления на процедуру обработки исключения, все прерывания запрещаются аппаратным способом. Пролог выполняет все функции, которые должны быть защищены от возможного прерывания более высоким приоритетом. Обычно это сохранение регистров и передача диагностической информации из модуля обработки операций с плавающй точкой в память. Когда критический процесс будет завершен, пролог может разрешить прерывания для того, чтобы позволить обработчикам прерываний с более высоким приоритетом выполниться раньше обработчика исключения.
Тело обработчика исключения просматривает диагностическую информацию и реагирует на то, что необходимо с точки зрения программного продукта. Эта реакция может иметь диапазон от прерывания выполнения для того, чтобы выдать сообщение, до попытки уладить неполадку и продолжить нормальное выполнение.
Эпилог, собственно, выполняет действия обратные прологу, возвращая процессор в состояние, при котором можно было бы возобновить нормальное выполнение. Эпилог не должен загружать немаскированный флаг исключения в модуль обработки операций с плавающей точкой или потребовать немедленно другой исключительной ситуации.
На рисунках от Рисунка 20-3 до Рисунка 20-5 показаны скелетные схемы исходных кодов на ассемблере ASM386/486 трех обработчиков исключительных ситуаций. При этом демонстрируется как могут быть написаны пролог и эпилог в различных ситуациях, а на месте, где должно быть помещено зависящее от применения тело обработчика исключения, стоят комментарии.
+---------------------------------------------------------------+| || SAVE_ALL PROC || ; || ; Сохранить регистры, занести || ; отображение состояния FPU в стек || ; || PUSH EBP || MOV EBP, ESP || SUB ESP, 108 || ; || ; Сохранить полное состояние FPU, разрешить прерывания || ; || FNSAVE [EBP-108] || STI || ; || ; Далее следует программа обработчика исключений || ; в зависимости от цели обработки || ; || ; Очистить флаги исключений в слове состояния || ; (которое находится в памяти) || ; Восстановить модифицированное отображение состояния || ; || MOV BYTE PTR [EBP-104], 0H || FRSTOR [EBP-108] || ; || ; Освободить стек, восстановить регистры || ; || MOVE ESP, EBP || . || . || POP EBP || ; || ; Вернуться к прерванным вычислениям || ; || IRET || SAVE_ALL ENDP || |+---------------------------------------------------------------+ Рисунок 20-3. Обработчик Исключения Полного Состояния +---------------------------------------------------------------+| || SAVE_ENVIRONMENT PROC || ; || ; Сохранить регистры, занести || ; окружение FPU в стек || ; || PUSH EBP || || MOV EBP, ESP || SUB ESP, 28 || ; || ; Сохранить окружение FPU, разрешить прерывания || ; || FNSTENV [EBP-28] || STI || ; || ; Далее следует программа обработчика исключений || ; в зависимости от цели обработки || ; || ; Очистить флаги исключений в слове состояния || ; (которое находится в памяти) || ; Восстановить модифицированное отображение окружения || ; || MOV BYTE PTR [EBP-24], 0H || FLDENV [EBP-28] || ; || ; Освободить стек, восстановить регистры || ; || MOVE ESP, EBP || POP EBP || ; || ; Вернуться к прерванным вычислениям || ; || IRET || SAVE_ENVIRONMENT ENDP || |+---------------------------------------------------------------+ Рисунок 20-4. Обработчик Исключения Сокращенного Времени Ожидания
Тексты на Рисунках 20-3 и 20-4 очень похожи; их отличие заключается только в выборе команд для сохранения и восстановления состояния модуля обработки операций с плавающей точкой. Выбор здесь заключается в повышенной диагностической информации, предоставляемой командой FNSAVE, и более быстрым выполнением команды FNSTENV. Для тех приложений, которые чувствительны к времени ожидания прерывания или которым не надо просматривать содержимое регистров, команда FNSTENV сокращает продолжительность "критической зоны", в течение которой процессор не видит требования других прерываний.
После выполнения тела обработчика исключения, эпилог приготавливает процессор для того, чтобы продолжить выполнение от точки прерывания (то есть, команды, следующей за той, которая создала немаскированное исключение). Обратите внимание, что флаги исключения в памяти, которые загружается в модуль обработки операций с плавающей точкой, обращаются в нуль для перезагрузки (фактически, в этих примерах обнуляется все отображение слова состояния).
Примеры на Рисунках 20-3 и 20-4 предполагают, что сам обработчик исключения не будет причиной другой немаскируемого исключения. Там, где это возможно, можно применять общий подход, показанный на Рисунке 20-5. Основной прием - сохранить полное состояние модуля обработки операций с плавающей точкой и затем загрузить новое управляющее слово в прологе. Обращаем ваше внимание на то, что надо быть очень осторожным при разработке обработчиков исключений этого типа для того, чтобы уберечь обработчик от бесконечных обращений.
+---------------------------------------------------------------+| || . || . || . || LOCAL_CONTROL DW ? ; Инициализировано || . || . || . || REENTRANT PROC || ; || ; Сохранить регистры, занести || ; отображение состояния FPU в стек || ; || PUSH EBP || . || . || . || MOV EBP, ESP || SUB ESP, 108 || ; || ; Сохранить полное состояние FPU, загрузить новое || ; управляющее слово, разрешить прерывания || ; || FNSAVE [EBP-108] || FLDCW LOCAL_CONTROL || STI || . || . || . || ; || ; Далее следует программа обработчика исключений || ; в зависимости от цели обработки || ; Генерируемое здесь немаскированное исключение || ; будет причиной вызова обработчика исключений. || ; Если необходима локальная память, то можно || ; расположить данные в стеке. || . || . || . || ; Очистить флаги исключений в слове состояния || ; (которое находится в памяти) || ; Восстановить модифицированное отображение состояния || ; || MOV BYTE PTR [EBP-104], 0H || FRSTOR [EBP-108] || ; || ; Освободить стек, восстановить регистры || ; || MOVE ESP, EBP || . || . || POP EBP || ; || ; Вернуться в точку, где произошло прерывание || ; || IRET || REENTRANT ENDP || |+---------------------------------------------------------------+ Рисунок 20-5. Реентерабельный Обработчик Исключений
Пример перевода числа с плавающей точкой в символьный вид
Вычислительные программы должны формировать свои результаты для того, чтобы они были воспринимаемы и читаемы пользователем программы. В большинстве случаев, числовые результаты выводятся как строки символов ASCII для того, чтобы их можно было отпечатать или просмотреть на дисплее. Этот пример показывает, как величина с плавающей точкой может быть переведена в десятичную строку ASCII символов. Функция, показанная на Рисунке 20-6, может быть вызвана из программ на языках PL/M-386/486, Pascal386/486, FORTRAN-386/486 или ASM386/486.
При написании программы обращалось внимание скорее на краткость, скорость и точность выполнения, чем на максимизацию числа значащих цифр. Сделана попытка оставить целые в их собственных областях для того, чтобы предотвратить случайные ошибки при переводе чисел.
Используя числа в вещественном формате расширенной точности, эта подпрограмма достигает в наихудшем случае точности три единицы в 16-том десятичном разряде для нецелелочисленных величин или целых чисел, больших чем 10**(18). Это двойная точность. Если число имеет десятичный порядок, меньший чем 100, точность составляет одна единица в 17-том десятичном разряде.
Более высокая точность может быть достигнута при больших затратах на программирование, увеличении размера программы более низкой эффективности.