Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

Об исключенных командах или за что «списали» инструкцию INTO?

Введение

Введение

            Говорят, и это почти не шутка, что для того, чтобы хорошо понять язык программирования, нужно написать транслятор с него. Тогда по аналогии, чтобы лучше понять систему команд процессора, нужно написать транслятор с ассемблера. Как раз некоторое время назад автор занялся доработкой своего транслятора с ассемблера, вводя новый для себя режим x86-64. Поскольку этот ассемблер имеет специальные макросредства ввода новых команд [1], его переделка не была сложной, хотя и потребовала добавления директивы «REX» для формирования кодов REX-префиксов. Но статья не об этом. При систематизации расширений системы команд в режиме x86-64 внимание привлек ряд «выбывших» (retired) команд, «уволенных в отставку» разработчиками архитектуры AMD64. Я не нашел в Интернете каких-либо документов, объясняющих это решение. Вероятно, самим разработчикам причина казалась вполне очевидной: данные команды «устарели» и практически нигде не используются. Рассмотрим, так ли это.

Исключенные команды

            Согласно документации Intel [2] в список выбывших в режиме x86-64 команд вошли:
  • команды «далекого» вызова и перехода CALLF и JMPF;
  • команда запроса уровня привилегий ARPL;
  • команды манипулирования сегментными регистрами DS, ES, SS;
  • команды запоминания/восстановления всех регистров PUSHAD и POPAD;
  • команда контроля выхода индекса массива за границы BOUND;
  • команды двоично-десятичной арифметики AAA, AAS, AAM, AAD, DAA, DAS;
  • команда контроля целочисленного переполнения INTO.
            Наличие в этом списке команд CALLF и JMPF является формальностью. Команды «далекого» вызова и перехода возможны и в режиме x86-64, просто исключаются эти конкретные формы с кодами 9A и EA. На мой взгляд, и их можно было бы оставить как более короткие и разрешить добавку REX-префикса, но это уже придирки.

            Системной командой ARPL большинство «обычных» программистов (включая автора) никогда не пользовалось, и целесообразность её исключения пусть оценивают программисты, пишущие ядра ОС.

            Исключение команд манипуляции сегментными регистрами DS, ES, SS, важных когда-то в эпоху MS-DOS, тоже не вызывает никаких сожалений, тем более, что разработчики архитектуры все-таки оставили возможность и в режиме x86-64 «раскрашивать» данные с помощью сегментных регистров FS и GS, что, например, может быть нужно при работе параллельных потоков в задаче.

            Таким образом, остается рассмотреть ценность наличия команд запоминания и восстановления всех регистров, контроля индексов, двоично-десятичной арифметики и контроля целочисленного переполнения.

Команды запоминания и восстановления всех регистров PUSHAD и POPAD

На первый взгляд кажется, что эти команды часто нужны. Но, например, проанализировав используемую системную библиотеку, я нашел лишь одно такое место, да и то в процедуре отладочной выдачи. Там это сделано для того, чтобы выдачу можно было вставить в любое место, даже посередине выражения, не влияя на среду. Т.е. команды запоминания и восстановления среды используются сейчас гораздо меньше, чем в эпоху MS-DOS. Поэтому в программах, хотя и громоздко перечислять имена всех регистров, но, к счастью, делать это нужно достаточно редко. Кроме того, в ассемблере [1] есть макрокоманда, позволяющая записывать несколько команд одной псевдоинструкцией и аналогичная ей макрокоманда восстановления, например:
PUSH EAX,EBX,ECX,EDX,ESI,EDI
POP EDI,ESI,EDX,ECX,EBX,EAX
            Конечно, немного неудобно, но выбрасывание команд PUSHAD и POPAD — это мелочь, из-за которой не стоило бы писать статью.

Команда контроля индексов массива BOUND

            Напомню, данная команда предполагает, что в регистр засылается индекс массива (со знаком), а адресная часть указывает на память, содержащую пару границ, на попадание внутрь которых содержимое данного регистра и проверяется. В случае выхода индекса из этих границ генерируется специальное исключение INT 5. Однако иногда возникают некоторые трудности с применением этой команды. Рассмотрим простейший пример доступа к элементу двумерного массива на (так любимом автором) языке PL/1 [3]:
DCL X(1:100,1:100) FLOAT(53);
DCL (I,J) FIXED(31);
X(I,J)=1e0;
            Транслятор (в режиме без контроля индексов) преобразует это в следующие команды:
imul    edi,I,800
mov     eax,J
lea     edi,X+0FFFFFCD8h[edi+eax*8]
mov     esi,offset const_1e0
movsd
movsd
            Видно, что старший индекс I появляется в регистрах, так сказать, не в «чистом» виде, а уже как результат умножения на предыдущую размерность. Кроме этого, вместо индекса-переменной в программе может быть выражение. Поэтому часто эффективнее (без отдельной загрузки только для контроля) проверять правильность не самого индекса, а некоторого предвычисленного значения. Но в таком случае предварительно надо проверять на переполнение и результат умножения. Причем нежелательно при переполнении вызывать исключительную ситуацию с другим номером, чтобы не вводить программиста в заблуждение относительно первопричины ошибки. Несмотря на такие тонкости, использование инструкции BOUND позволяет на аппаратном уровне выполнить две проверки за один раз. Отсутствие этой команды потребует или вставлять собственные процедуры проверок и раздувать код программы или, оставив в программе запрещенную теперь BOUND, обрабатывать исключение от неё самой и эмулировать её работу. Но в этом случае каждая проверка будет вызывать исключение, а раньше — только редкий реальный выход индекса за допустимые рамки. Как говорится, почувствуйте разницу. Хотя признаюсь, в своих программах по историческим причинам я не использую BOUND, а применяю прямую проверку результата умножения в паре регистров EDX:EAX. Этот несколько архаичный прием позволяет отказаться от дополнительной проверки переполнения, поскольку сравнение идет сразу с «расширенными» границами, также умноженными на предыдущую размерность. Тогда вот во что выливается предыдущий пример в режиме с проверкой индексов, но без использования BOUND:
mov    eax,800
imul   I
mov    edi,eax
push   800
push   80000
call   ?serv3
mov    eax,8
imul   J
add    edi,eax
push   8
push   800
call   ?serv3
lea    edi,X+0FFFFFCD8h[edi]
mov    esi,offset const_1e0
movs
movs
            Внутри вставляемого транслятором вызова системной подпрограммы ?SERV3 значение в EDX:EAX проверяется на попадание в диапазон двух 8-байтных значений, передаваемых через стек. Причем для компактности кода транслятор выбирает эту подпрограмму в случае, когда старшие байты результата умножения границ на размерность равны нулю, и через стек передает ей только младшие байты:
public ?serv3:

      push     0               ;добавляем ноль как старшие байты

;---- не ниже нижней границы ? ----

      sub       [esp]+0ch,eax
      sbb       [esp]+000,edx
      jl        @
      cmp       d ptr [esp]+0ch,0
      jnz       err

;---- не выше верхней границы ? ----

@:    mov       d ptr [esp],0
      sub       [esp]+08h,eax
      sbb       [esp]+000,edx
      jl        err

;---- контроль прошел нормально ----

      add       esp,4           ;выбросили ноль из стека
      ret       8

;---- исключение по выходу индекса за границы ----

err: …
            Но такой подход — это, скорее, исключение из правила, а, в общем, безусловно, использование BOUND позволяет упростить код и избежать условных переходов. В приведенном примере как раз и видно, насколько громоздким становится контроль индексов без использования аппаратных средств.

Команды двоично-десятичной арифметики

            Судя по компьютерным форумам, не все представляют, как именно используются команды двоично-десятичной арифметики, и главное, зачем они вообще введены. Иногда говорят, что это нужно для арифметики чисел, записанных в виде текста, чтобы не переводить в двоичный вид и обратно. Это не так. Данные команды работают не с текстовым представлением чисел, а со специальным двоично-десятичным кодом (BCD). Основное его назначение — точное представление чисел с дробной частью, представленной в виде десятичной дроби. В таком виде можно проводить арифметические вычисления без округлений и потери точности, а это важно в некоторых научных и особенно финансово-экономических расчетах с их сложными процентами. Тогда зачем ввели целых 6 команд и два BCD-формата (упакованный и неупакованный)? Дело в том, что пытались найти компромисс между скоростью и размером чисел в памяти. Арифметика чисел в неупакованном BCD-формате проще и быстрее (используя команды AAA, AAS, AAM, AAD), зато число из 15 цифр с учетом знака занимает целых 16 байт. Во времена дорогой памяти казалось, что это слишком много. Наоборот, упакованный BCD-формат довольно «плотный». Число из 15 цифр занимает 8 байт. Сравните с 8-байтным числом в формате IEEE 754, где имеется лишь чуть больше десятичных цифр мантиссы 253 ~15.955, но при этом представление числа приближенное, а в BCD-формате — точное. По сравнению с неупакованным BCD-форматом, в упакованном формате сложнее вычислять умножение и деление. Поэтому и есть только команды DAA и DAS, а DAM и DAD никогда не существовало. Создатели используемого мною PL/1 сделали выбор в пользу упакованного BCD-формата, хотя в ретроспективе (память стала гораздо дешевле) возможно это не оптимальное решение. Но при существующем положении дел меня задевает исключение именно DAA и DAS. Для примера вот текст самой простой системной подпрограммы сложения двух BCD-чисел в стеке, вызов которой генерируется транслятором:
public ?dadop:                 ;вызов подставляется транслятором PL/1
      lea       esi,[esp]+4    ;начало 1-го BCD-числа
      mov       ecx,8          ;максимальная длина числа в байтах
      clc
      lea       edi,[esi+ecx]  ;начало 2-го BCD-числа

;---- цикл сложения двух BCD-чисел в стеке ----

m:    lodsb
      adc       al,[edi]       ;сложение с коррекцией
      daa
      stosb                    ;запись ответа
      loop      m

;---- проверка на переполнение ----

      and       al,0f0h        ;выделили последнюю цифру ?
      jz        @
      cmp       al,90h         ;отрицательный не переполнился ?
      jnz       ?dover         ;overflow для объекта типа decimal

;---- выход с очисткой стека ----

@:    pop       ecx
      mov       esp,esi        ;очистка стека
      jmp       ecx
            Здесь складываются BCD-числа с точностью 15 десятичных цифр. Они могут иметь дробную часть, тогда за положением десятичной точки результата следит сам транслятор. При этом ничто не мешает организовать сложение с точностью, например, 150 или даже 1500 цифр. Для этого в данной подпрограмме надо лишь заменить константу 8 на значение 76 или 751. В этом главная особенность и смысл существования всего аппарата двоично-десятичной арифметики: возможность вычислений без округлений с любой заданной точностью. Такой аппарат для финансово-экономических расчетов появился во времена Кобола, т.е. очень давно используется и давно отработан. Вернемся к режиму x86-64. Исключение команд двоично-десятичной арифметики выглядит здесь какой-то «экономией на спичках». Например, взять те же DAA и DAS. Они однобайтные, не имеют параметров и (редкий случай!) не имеют исключительных ситуаций. Т.е. обрабатываются в процессоре самым простым образом. Зачем же было отключать несложную и эффективную аппаратную поддержку целого класса объектов и задач? Неужели только ради убирания нескольких примитивных инструкций? На фоне существования десятков различных типов команд, вроде SSE4, такое упрощение ничтожно. Причем эти инструкции все равно остаются в процессоре в 32-разрядном режиме. Теперь придется эмулировать их в соответствии с алгоритмами из официальной документации. Хорошо хоть флаг AF (перенос из младшей тетрады) разработчики сохранили в режиме x86-64, хотя там уже нет команд, его использующих. Без этого флага эмуляция стала бы ещё более громоздкой.

Команда контроля целочисленного переполнения

            Выбрасывание команды INTO в режиме x86-64 явилось для меня очень неприятным открытием. Хотя данная инструкция самая простая для эмуляции и может быть заменена всего одной командой условного перехода по флагу переполнения, главное преимущество существующей инструкции именно в том, что она аппаратная. Так же, как и в случае BOUND, эмуляция приведет к появлению или ветвлений в программе или к потоку исключений, что резко снижает эффективность выполнения. А существующая аппаратная реализация просто декодирует байт CE и в подавляющем большинстве случаев пропускает его, почти не нагружая конвейеры, предсказатель переходов и прочие внутренние элементы процессора. Удобна эта инструкция и для работы транслятора. Он просто дописывает к кодам команд, требующим контроля переполнения, байт с кодом CE и дело с концом.

            Например, сейчас в одном из основных проектов из числа тех, которые я сопровождаю, данная инструкция встречается в разных модулях более 3600 раз (при общей длине команд проекта 1.531.453 байт) и является штатным средством контроля правильности вычислений. Таким образом, увеличив длину кодов программы всего лишь на 0.2%, я получаю постоянный аппаратный контроль без ощутимого уменьшения скорости исполнения. Т.е. я вообще не могу заметить изменение скорости выполнения от использования данных команд. Вероятно, оно тоже составляет малые доли процента. И вспоминаю я об этом постоянном контроле только тогда, когда он срабатывает. За много лет эксплуатации программ десятки реальных случаев срабатываний исключений от инструкции INTO не раз помогали быстрее разбираться с ошибками, поскольку не давали ошибочным результатам распространяться по программе и замаскировать истинную причину ошибки.

Заключение

            Итак, развитие архитектуры процессоров объективно приводит к тому, что некоторые команды и приемы программирования становятся излишними, и в новых условиях, например, в режиме x86-64, их можно исключить. Однако, с моей точки зрения, в ряде случаев с водой выплеснули и ребенка. Решение исключить в режиме x86-64 инструкции DAA, DAS, BOUND и INTO следует признать недальновидным. Эти команды эффективно и простыми средствами осуществляли аппаратную поддержку таких действий программиста, которые остаются актуальными и в новых условиях. Конечно, работу исключенных инструкций всегда возможно описать оставшимися командами. Но таким путем со временем можно дойти и до машины Тьюринга. Рассмотренные инструкции уменьшают необходимость применения условных переходов в программе, чему в архитектуре х86 всегда придавалось особое значение. Об этом, например, свидетельствует наличие целой группы команд типа CMOV. Непонятно и какой выигрыш в архитектуре ожидали получить разработчики. «Освободившиеся» коды операций, за исключением кода ARPL, в режиме x86-64 никак не используются.

            Есть и ещё одна сторона этого дела, вроде и не имеющая отношения к технике. Лично я испытываю психологический дискомфорт оттого, что исчезают команды, к которым привык, и которые постоянно используются в программах. Их удобство, эффективность, а потому и целесообразность была многократно доказана и в учебной и в справочной литературе, и на практике. И вдруг некто, не снисходя до объяснения причин в доступной литературе, объявляет их лишними. Это выглядит бездоказательно. А свои доказательства я привел в данной статье. Получается, что «совершенствование» архитектуры приводит не к улучшению, а к деградации эффективности выполнения ряда действий, которые много лет исправно «несли службу» в тысячах программ. Особенно обидна потеря старых удобных средств на фоне бесконечного добавления все новых и новых команд, общее число которых в архитектуре x86-64 по моим подсчетам уже перевалило за 630.

            Если верить Википедии, такая же судьба сначала постигла и команды LAHF/SAHF, но потом они были все-таки возвращены. Было бы правильным вернуть и команды DAA, DAS, BOUND, а особенно INTO в архитектуру x86-64. Эту инструкцию «списали» ни за что.

Литература

1. Караваев Д.Ю. О специальных макросредствах в трансляторе с языка ассемблера RSDN Magazine #3, 2012
2. Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 2B. Order Number:253667-027US, April 2008
3. Караваев Д.Ю. К вопросу о совершенствовании языка программирования RSDN Magazine #4, 2011

Автор: Д.Ю.Караваев. 23.01.2014

Опубликовано: 2018.08.26, последняя правка: 2019.01.29    14:26

ОценитеОценки посетителей
   ██████████████████ 5 (41.6%)
   ███████ 2 (16.6%)
   ███████ 2 (16.6%)
   ███████████ 3 (25%)

Отзывы

✅  2018/10/04 17:39, Автор сайта          #0 

Исключение INTO и BOUND из системы команд приводит к тому, что проверка корректности производится в ущерб эффективности. При существующем стремительном увеличении объёма ПО вопрос его надёжности становится всё важнее. Тут мог бы помочь аппаратный контроль корректности, а они делают противоположное. При Сталине такого не было..

✅  2018/10/22 16:31, Comdiv          #1 

эмуляция INTO приведет к появлению или ветвлений в программе или к потоку исключений, что резко снижает эффективность выполнения.

Насколько резко? Мои опыты показывают разницу для умножения 32-битных чисел со знаком в ~1.3 раза от бесконтрольного умножения и в ~2 раза от сложения. При этом сложение в 32-битном режиме также в 2 раза медленнее, чем в 64-битном. В целом приложении эта разница вообще не ощущается. Можете привести числа для команды INTO?

✅  2018/10/25 06:15, kt          #2 

Для команды INTO утверждается, что вообще пренебрежимо малые затраты времени. Что касается тестов — я считаю, в данном случае нужно анализировать сгенерированный код. Точно ли используется INTO или проверки идут иначе? Кроме этого, тесты часто грешат тем, что миллионы раз выполняется одна и та же операция подряд, что в реальных программах — редкость.

✅  2018/10/29 16:27, Comdiv          #3 

В проведённых мною тестах не использовались команды INTO. Компилятор gcc не вставлял их в обоих режимах трансляции — как для 32, так и для 64 разрядных. Он использовал команды JO. Поэтому я и спрашивал про измерения производительности с INTO. Утверждения — это одно, результаты замеров — другое.

Если же брать не синтетические тесты, то разница и с JO не заметна.

✅  2018/10/29 20:52, kt          #4 

Не очень я доверяю таким тестам, но извольте:
TEST:PROC MAIN;
DCL (I,J,K) FIXED(31);

PUT SKIP DATA(TIME);
DO I=1 TO 100_000;
DO J=1 TO 100_000;
K=I+J;
END J;
END I;
PUT SKIP DATA(TIME);

END TEST;
Вот такой код генерируется

5 c 000034 DO I=1 TO 100_000;
000034 C7050800000001000000 mov I,1
00003E @2:
00003E 813D08000000A0860100 cmp I,100000
000048 7F39 jg @3
6 d 00004A DO J=1 TO 100_000;
00004A C7050C00000001000000 mov J,1
000054 @4:
000054 813D0C000000A0860100 cmp J,100000
00005E 7F1A jg @5
7 e 000060 K=I+J;
000060 A108000000 mov eax,I
000065 03050C000000CE add eax,J
00006C A310000000 mov K,eax
000071 FF050C000000CE inc J
000078 EBDA jmps @4
00007A @5:
00007A FF0508000000CE inc I
000081 EBBB jmps @2
000083 @3:
8 c 000083 END J;

Для процессора i5-3210M 2.5 GHz
Без INTO                      с INTO
TIME= 20:27:09 TIME= 20:34:24
TIME= 20:27:31 TIME= 20:35:30
22 секунды против 65 секунд, итого (65-22)/2=21.5 дополнительных секунд на каждый INTO (третий INTO вызывается в сто тысяч раз меньше, им пренебрегаем). Почти удвоение или 98%.

Для процессора U1500 1.33.GHz
Без INTO                      с INTO
TIME= 19:32:27 TIME= 19:21:09
TIME= 19:33:20 TIME= 19:23:14
53 секунды против 125 секунд, итого (125-53)/2=36 секунд на каждый INTO или 68%.

Похоже, в 64-разрядном процессоре и в режиме 32 разряда INTO работает хуже. Но ещё учтите, что каждый условный переход ещё на 4 байта длинее INTO и код становится менее плотным, что действует на кэш.

✅  2018/10/30 02:03, Comdiv          #5 

Спасибо. Согласен, доверять тестам стоит с осторожностью, но это всё же лучше, чем ничего.

✅  2018/10/30 06:04, kt          #6 

В целом, коррелирует со старым справочником (у меня 1995 года) по x86. Например, там указано для 486 INTO, случай "fail" (т.е. нет переполнения) 3 такта и ADD тоже 3 такта. Потом фирма Intel выкинула из своей документации такты от греха подальше, очевидно, чтобы въедливые пользователи задавали поменьше вопросов по оптимизации всяких "гипер-конвейреров".

✅  2018/10/30 11:56, Автор сайта          #7 

Ещё было бы интересно заменить INTO на команду условного перехода и опять сделать замеры. Ведь изначальный посыл в том, что инструкции INTO в 64-битном режиме нет, а от замены INTO на условный переход код становится медленнее. Кстати, в приведённом выше сгенерированном коде инструкции INTO не наблюдается — как тогда INTO заменить на JO?

✅  2018/10/30 13:01, kt          #8 

Наблюдается, наблюдается )) Это байт CE в конце трех команд. В статье, кстати, описка байт вместо CE написан как EC.

Для 64-разрядного режима у меня по ряду причин генерируется не переход, в служебный вызов (процессор i5-4570 3.20 GHz):
       000034                         @1:
5 c 000034 DO I=1 TO 100_000;
000034 C7051000000001000000 mov I,1
00003E C70520000000A0860100 mov @00000020h,100000
000048 @2:
000048 A12000000000000000 mov eax,@00000020h
000051 390510000000 cmp I,eax
000057 7F5C jg @3
6 d 000059 DO J=1 TO 100_000;
000059 C7051400000001000000 mov J,1
000063 C70524000000A0860100 mov @00000024h,100000
00006D @4:
00006D A12400000000000000 mov eax,@00000024h
000076 390514000000 cmp J,eax
00007C 7F2A jg @5
7 e 00007E K=I+J;
00007E A11000000000000000 mov eax,I
000087 030514000000 add eax,J
00008D E800000000 call ?EINTO
000092 A31800000000000000 mov K,eax
00009B FF0514000000 inc J
0000A1 E800000000 call ?EINTO
0000A6 EBC5 jmps @4
0000A8 @5:
0000A8 FF0510000000 inc I
0000AE E800000000 call ?EINTO
0000B3 EB93 jmps @2
0000B5 @3:

без контроля переполнения — 18 секунд
TIME= 12:46:04
TIME= 12:46:22

один из вариантов контроля без INTO — 28 секунд
TIME= 12:43:08
TIME= 12:43:36

✅  2018/10/30 16:20, Автор сайта          #9 

Наблюдается, наблюдается )) Это байт CE в конце трех команд.

Оказалось ненаглядным :(

✅  2018/10/31 06:10, kt          #10 

Аппаратная команда INTO изначально задумывалась, на мой взгляд, как ускорение за счет отсутствия ветвления в программе. Заодно как и более короткая команда без операндов. Тесты же плохи тем, что обращение к переменным идет одно и то же, а значит, вместо медленной памяти идет обращение к быстрому кэшу. Поэтому в тесте очень мало тратится времени на все команды. И у меня получилось, что относительный вес INTO в 64-разрядных процессорах больше, чем в 32-разрядных.

Получилось, что сложение и INTO занимают одинаковое время. В реальности будут промахи и арифметика будет медленнее INTO.

✅  2018/10/31 12:55, Comdiv          #11 

Промахи случаются не так часто. Коллега, разрабатывающий на verilog, говорил мне, что обращение к медленной памяти занимает порядка 50 тактов (со всеми оговорками). Если бы промахи случались слишком часто, мы бы все были ошеломлены скоростью наших программ.

✅  2018/10/31 14:26, Автор сайта          #12 

По хорошему, надо писать большие и разнообразные тесты, тогда можно получить картину почётче.

обращение к медленной памяти занимает порядка 50 тактов

В современных процессорах количество транзисторов превысило 2 млрд. Логичный вопрос: почему бы не сделать память по той же технологии и на том же кристалле, чтобы обращение к памяти было столь же быстрым, как и к регистрам? На один бит нужен один триггер из двух транзисторов. Т.е. 16 млрд. транзисторов дадут 1 Гб памяти.

мы бы все были ошеломлены скоростью наших программ

Надо полагать, ошеломлены в плохом смысле.

✅  2018/10/31 16:18, kt          #13 

И тесты, конечно, можно всякие написать. Но остаюсь при своем мнении: лучше бы не трогали INTO грязными лапами. И всем (а особенно в PL/1)было бы хорошо.
Вполне вероятно, что команда INTO своим появлением в x86 обязана именно PL/1, где изначально предусматривалось исключение FIXEDOVERFLOW. Т.е. INTO нужна именно для формирования сигнала исключения, поскольку флаг переполнения сам по себе прерывания не возбуждает.

✅  2018/11/01 12:40, Автор сайта          #14 

Вполне вероятно, что команда INTO своим появлением в x86 обязана именно PL/1, где изначально предусматривалось исключение FIXEDOVERFLOW

Вообще то PL/1 — это детище IBM. Но и система 360/370 — тоже детище IBM. И достаточно долгие годы PL/1 существовал только на платформе IBM/360/370. Был, если так можно выразиться, «машинно-ориентированным языком высокого уровня». В системе IBM/360/370 при появлении переполнения (как и многих других событий типа деления на ноль) сразу происходило прерывание. Не важно, на чём ты пишешь, на ассемблере, Фортране или PL/1 — прерывание было неизбежно и не требовало дополнительных команд. Закрыть (выставить запрещающую прерывание маску) его можно было только на уровне привилегий 0, это режим ядра.

Полагаю, что в архитектуре Intel больше ориентировался на PDP-11. Именно с PDP-11 и языка Си пошла мода не реагировать на переполнения. В архитектуре IBM не было стека, в PDP и Intel он есть. Даже название команд говорит о сходстве с PDP и несходстве с IBM.
IBM             PDP     Intel    Команда
=== === ===== =======
L, LD, LR, LA MOVE MOV Загрузка значения (LOAD)
S, ST, MOVE MOV Сохранение значения (STORE)
B, BR JUMP JMP Переход (BRANCH)
BAL, BALR CALL CALL Вызов подпрограммы (BRANCH AND LINK)
A, AR ADD ADD Сложение
S, SR SUB SUB Вычитание
FIXEDOVERFLOW (мне так помнится) указывало операционной системе обработчик прерывания. В случае переполнения срабатывал этот обработчик, но программа потом могла работать дальше. При отсутствии обработчика программа просто завершала выполнение, а в листинге выдавался код прерывания.

Мне кажется, что такая стратегия была правильна. Но... В своё время профессионалы эпохи мейнфреймов сравнивали наступление эпохи ПК с нашествием варваров на древний Рим: взамен взвешенных архитектурных решений пошли в ход тяп-ляп архитектуры и сделанное на коленках ПО. Упадок науки и искусства, тожество вульгарности и безвкусицы... Раньше ПО шло в комплекте с «железом», за качество и того, и другого отвечал производитель. Но потом ПО стали продавать отдельно, и чтобы не отвечать за качество, придумали принцип «as is»...

✅  2018/11/01 13:33, kt          #15 

Лично я не считаю x86 непродуманной архитектурой, её создавали серьезные специалисты (и, кстати, Интел отпочковался из ИБМ). Там есть, конечно, ляпсусы, вроде флага после выполнения XOR, но ошибки бывают у всех. Аббревиатуры команд, да, раньше были не такие, но в стародавние времена перфокарт все старались сократить донельзя.
Конкретно же в случае целочисленного переполнения логика разработки могла быть такой: для адресной арифметики переполнение дает заворот и хорошо. Для неадресной арифметики переполнение должно давать исключение — сделаем специальную команду, которую транслятор и расставит где надо. На мой взгляд, INTO можно было сделать вообще префиксом, вроде LOCK, но и так не плохо. Кстати, в моем компиляторе есть две команды сложения (таблица в строке 280 файл PL1A) «add q» и «add». Они отличаются только необходимостью контроля переполнения, в случае «add q» транслятор никогда не добавляет INTO — поскольку это сложение для получения адреса.

✅  2018/11/01 15:19, Автор сайта          #16 

Интел отпочковался из ИБМ

А вот и не так. Рассказываю: «Авраам родил Исаака; Исаак родил Иакова; Иаков родил Иуду и братьев его»…

Уильям Шокли, выходец из Bell Labs, создал в 1956 году Shockley Semiconductor Laboratory. Осенью 1957 «вероломная восьмёрка» (восемь молодых сотрудников, недавних выпускников вуза) уволилась из Shockley Semiconductor Laboratory и основала свою собственную компанию, Fairchild Semiconductor. Компанию Intel в1968 году основали Роберт Нойс и Гордон Мур, ушедшие из Fairchild Semiconductor.

— это из Википедии.

не считаю x86 непродуманной архитектурой

Порядок байтов в x86 можно было бы сделать нормальным. Они же решили идти оп пути DEC, родившей PDP-11.

для адресной арифметики переполнение дает заворот и хорошо

Так это тоже неправильно. Это же ошибка — выход за пределы адресного пространства.

✅  2018/11/01 16:32, kt          #17 

Вовсе нет. Вот простейший пример
DCL X(1:100) FIXED(31);
DCL I FIXED(31);
X(I)=0;
Чтобы получить адрес с индексом 1 нужно «отступить» на минус 4 байта. Как раз сложение с заворотом
A190010000                  mov    eax,I
832485FCFFFFFF00 and X+0FFFFFFFCh[eax*4],0

✅  2018/11/01 19:10, Автор сайта          #18 

«Отступить» на минус 4 байта — это -(-4) или же -(FFFFFFFC). А у Вас написано X+FFFFFFFC. Т.е. X+(-4). Это — «пройти вперёд» на минус 4 байта!

Добавить свой отзыв

Написать автору можно на электронную почту
mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

●  О превращении кибернетики в шаманство

●  Про лебедей, раков и щук

●  О замысле и воплощении

●  О русском ассемблере

●  Арифметика синтаксиса-3

●  Концепция владения в Rust на примерах

●●  Концепция владения в Rust на примерах, часть 2

●●  Концепция владения в Rust на примерах, часть 3

●  Суть побочных эффектов в чисто функциональных языках

●  О неулучшаемой архитектуре процессоров

●  Двадцать тысяч строк кода, которые потрясут мир?

●  Почему владение/заимствование в Rust такое сложное?

●  Масштабируемые архитектуры программ

●  О создании языков

●●  Джоэл Спольски о функциональном программировании

●  Почему Хаскелл так мало используется в отрасли?

●  Программирование исчезнет. Будет дрессировка нейронных сетей

●  О глупости «программирования на естественном языке»

●  Десятка худших фич C#

●  Бесплатный софт в мышеловке

●  Исповедь правового нигилиста

●  ЕС ЭВМ — это измена, трусость и обман?

●  Русской операционной системой должна стать ReactOS

●  Почему обречён язык Форт

●  Программирование без программистов — это медицина без врачей

●  Электроника без электронщиков

●  Программисты-профессионалы и программирующие инженеры

●  Статьи Дмитрия Караваева

●●  Идеальный транслятор

●●  В защиту PL/1

●●  К вопросу о совершенствовании языка программирования

●●  Опыт самостоятельного развития средства программирования в РКК «Энергия»

●●  О реализации метода оптимизации при компиляции

●●  О реализации метода распределения регистров при компиляции

●●  О распределении памяти при выполнении теста Кнута

●●  Опыты со стеком или «чемпионат по выполнению теста Кнута»

●●  О размещении переменных в стеке

●●  Сколько проходов должно быть у транслятора?

●●  Чтение лексем

●●  Экстракоды при синтезе программ

●●  Об исключенных командах или за что «списали» инструкцию INTO?

●●  Типы в инженерных задачах

●●  Непрерывное компилирование

●●  Об одной реализации специализированных операторов ввода-вывода

●●  Особенности реализации структурной обработки исключений в Win64

●●  О русском языке в программировании

●●  Формула расчета точности для умножения

●●  Права доступа к переменным

●●  Заметки о выходе из функции без значения и зеркальности get и put

●●  Модификация исполняемого кода как способ реализации массивов с изменяемыми границами

●●  Ошибка при отсутствии выполняемых действий

●●  О PL/1 и почему в нём не зарезервированы ключевые слова

●●  Не поминайте всуе PL/1

●●  Скорость в попугаях

●●  Крах операции «Инкогнито»

●●  Предопределённый результат

●●  Поддержка профилирования кода программы на низком уровне

●●  К вопросу о парадигмах

●  Следующие 7000 языков программирования

●●  Что нового с 1966 года?

●●  Наблюдаемая эволюция языка программирования

●●  Ряд важных языков в 2017 году

●●  Слоны в комнате

●●  Следующие 7000 языков программирования: заключение

Компьютерный юмор

Новости и прочее




Последние отзывы

2024/12/07 20:54 ••• Клихальт
Переключатель

2024/12/06 18:44 ••• Анкнав
Русский язык и программирование

2024/12/01 00:00 ••• alextretyak
Продолжение цикла и выход из него

2024/11/29 23:08 ••• Вежливый Лис
О русском ассемблере

2024/11/26 23:53 ••• Бурановский дедушка
ЕС ЭВМ — это измена, трусость и обман?

2024/11/25 18:31 ••• Деньги на WWWетер
Ресурсы, посвящённые созданию языков программирования и компиляторов

2024/11/12 20:24 ••• Вежливый Лис
Правила языка: строки, комментарии

2024/11/12 13:10 ••• Вежливый Лис
Новости и прочее

2024/11/12 00:32 ••• Автор сайта
Оценка надёжности функции с несколькими реализациями

2024/11/06 02:50 ••• Иван
Энтузиасты-разработчики компиляторов и их проекты

2024/11/05 23:51 ••• Борис К.
Изменение приоритетов операций

2024/11/05 23:38 ••• Борис К.
Шестнадцатиричные и двоичные константы

2024/11/01 12:11 ••• ИванАс
Русской операционной системой должна стать ReactOS