vak: (Знайка)
[personal profile] vak
Наш эмулятор БЭСМ-6 развивается уже около 30 лет. За это время мы гоняли на нём кучу разного софта. Вдохновлённые успехами, мы верили, что уж систему команд-то мы поняли хорошо и реализовали качественно. И тут внезапно нежданчик. Вчера обнаружили ошибку в арифметике. Не в самих вычислениях, а в команде выдачи младшей половины результата арифметической операции. Расскажу по порядку.

Эмулятор БЭСМ-6 когда-то начинался с простой необходимости проверять качество кода, порождаемого Си-компилятором. Через некоторое время он развился до dispak, на котором уже можно запустить любую задачу режима пользователя. Если знаете, есть в линуксе такой Wine, на котором можно запускать приложения Windows. Здесь то же самое, только вместо системных вызовов win32 эмулируются экстракоды ОС Диспак.

Пятнадцать лет назад мы раздобыли образ системного диска реальной БЭСМ-6 и соорудили для его оживления более серьёзный эмулятор на основе SIMH. Здесь уже имитируется работа всей периферийной аппаратуры машины, в дополнение к процессору. Прогнали на этих эмуляторах вагон разных компиляторов, прикладных программ и утилит. Все выловленные баги аккуратно исправили до состояния приятной стабильности. Приделали графическую панель, чтобы наблюдать лампочки на пульте и знаменитое диспаковское "жду" на регистрах, когда операционке заняться нечем.



В прошлом году мы доросли до понимания устройства другой бэсмовской операционки, а именно ОС Дубна, и сваяли третий эмулятор dubna. Он работает по той же идее, что и dispak: выполняет системные вызовы и позволяет запускать задания точно в виде, описанном в исторической книжке Мазного "Программирование на БЭСМ-6 в системе Дубна". И тоже всё у нас отлично работало - пока мы не наткнулись на проблему со строками в Алголе.

В составе мониторной системы Дубна присутствует транслятор с языка Алгол-60. Его назвали Алгол-ГДР, чтобы отличать от другой реализации Алгол-БЭСМ, которая имелась только под Диспаком. Так вот, в Алголе-ГДР имелось несколько полезных расширений языка, по сравнению со стандартом Алгола-60. Одно из расширений - тип string. Память под строки автоматически выделяется в особом буфере, и в целом с ними довольно удобно работать. Все операции с алгольными строками отлично функционируют под эмулятором dispak. Но под dubna мы столкнулись с проблемой: элементарная операция индексации (вида s[5]) или среза (s[3:7]) приводила к останову по арифметическому переполнению. Простая программа:
*name Алгол
*no list
*no load list
*algol
'begin'
'string' s;
s := ''abra cadabra'';
print(s, newline);
print(s[3], newline);
print(s[2:8], newline);
'end''eop'
*execute
*end file
Запускаем на одном эмуляторе:
$ dispak algol.b6 
26.07.24 М1

М О Н И Т О Р-8 0 (3.7) 25.09.84* ( МС " Д У Б Н А " ИАЭ ) ШИФР 419999000000
...
*NАМЕ АЛГОЛ
*NО LISТ
*NО LОАD LISТ
*АLGОL
*ЕХЕСUТЕ
...
АВRА САDАВRА
R
ВRА САD
Запускаем на другом:
$ dubna algol.dub 
27 ИЮЛ 24 00.00
ЙOKCEЛ БЭCM-6/5 ШИФP-12
MOHИTOPHAЯ CИCTEMA ′Д Y Б H A′ - 20/10/88
...
*NAME AЛГOЛ
*NO LIST
*NO LOAD LIST
*ALGOL
PROGRAM ДЛИHA: 28 00034B BPEMЯ: 0.00 CEK. KAPT: 7
*EXECUTE
...
ABRA CADABRA
Error: Arithmetic overflow @01467
Что такое, в чём дело? Несколько дней ходили вокруг этой ошибки. Склонялись уже к тому, что в Дубне был сломанный Алгол. Что никто ним не пользовался, а тем более странными строками. Однако взялись копнуть глубже.

Сильная сторона эмулятора - возможность включить подробную трассировку. Включаем, смотрим где упало.
$ dubna -d remi algol.dub
...
01463 L: 14 005 0424 a-x 424(14)
Memory Read [02063] = 6400 0000 0000 0001
ACC = 6400 0000 0000 0002
RAU = 23
01463 R: 00 27 02046 u1a 2046
RMR = 6400 0000 0000 0002
01464 L: 14 017 0423 a*x 423(14)
Memory Read [02062] = 4002 5252 5252 5253
ACC = 6400 0000 0000 0000
RMR = 6405 2525 2525 2526
RAU = 13
01464 R: 17 000 0000 atx (17)
Memory Write [03504] = 6400 0000 0000 0000
M17 = 03505
01465 L: 00 037 0007 ntr 7
RAU = 07
01465 R: 00 031 0000 yta
ACC = 6405 2525 2525 2526
01466 L: 14 015 0414 aox 414(14)
Memory Read [02053] = 4000 0000 0000 0000
RMR = 0000 0000 0000 0000
01466 R: 14 011 0431 aax 431(14)
Memory Read [02070] = 7777 7777 7770 0000
ACC = 6405 2525 2520 0000
01467 L: 14 017 0420 a*x 420(14)
Memory Read [02057] = 6437 7777 7777 7772
Error: Arithmetic overflow @01467
ACC = 1037 7777 7777 7776
RMR = 0000 0000 0040 0000
00020 L: 00 074 0000 *74
Видим, что переполнение случилось в результате команды A*X, то есть умножения. Одно целое число 6405 2525 2520 0000 (слово в восьмеричном виде) умножалось на другое число 6437 7777 7777 7772. То, что числа целые, видно по характерному порядку 64 в старших битах. Режим арифметического устройства установлен в RAU=07, что означает отключение нормализации и округления. Без нормализации порядок 64 воспринимается как 240, то есть одно очень большое число умножается на другое очень большое число. Естественно, переполнение. Так не должно быть.

Откуда взялся порядок 64 у числа на сумматоре? Оно извлечено командой YTA из регистра младших разрядов. А образовалось это значение RMR в результате предыдущей команды умножения A*X. Причём позже на это значение командой побитового "или" (AOX) накладывается значение 4000 0000 0000 0000. То есть делается попытка восстановить биты порядка. И тут закралось первое подозрение. Очевидно, что команда AOX предполагает нулевые разряды порядка у значения в RMR после умножения. Откуда же там взялось 64? Может быть, этот код на ленте испорченный? Всякое бывает.

Если посмотреть по таблице адресов и имён загрузки программы, ошибка происходит в модуле A/SPLUSS. К счастью, у нас есть исходный текст этого модуля в файле besm6.github.io/sources/dubna/besmmons/system1.txt. Вот фрагмент:
  zerl6   :12 , a-x   , =i1-c. up i14.ac=6*a+b+1.a in mag,-8b in sh
, u1a , slicerr grenze negativ
l 12 , a*x , =4002 5252 5252 5253-c. 1/6 exp 64
15 , atx , ganzer teil
, ntr , 7 wegen yta
, yta ,
12 , aox , b48-c rest/6 als gleitkommazahl
l 12 , aax , =7777 7777 7770 0000-c. wegen rundung
12 , a*x , =i-6 -c -b dh neg rest mod 6
Тут всё честно, никаких расхождений. Комментарии на немецком: подтверждение, что Алгол-ГДР действительно делался немцами.

Смотрим реализацию арифметики в эмуляторе. Центральная процедура здесь arith_normalize_and_round(), которая вызывается в завершении каждой арифметической операции и выполняет завершающую нормализацию и округление. В конце видим:
    core.RMR = (core.RMR & ~BITS40) | (mr & BITS40);
То есть мантисса в регистре RMR получает новое значение, а биты порядка остаются старыми. Тут возникло второе подозрение. Зачем нужно держать в регистре мусор от какой-то предыдущей операции? Странно.

А что должно оказываться в битах порядка RMR? Открываем официальную документацию от БЭСМ-6. Смотрим последнюю строчку: "содержимое 41:48 разрядов регистра младших разрядов сохраняется". Чепуха выходит.


Но у нас есть и описание СВС, то есть машины следующего поколения, совместимой по системе команд с БЭСМ-6. Смотрим туда: "содержимое 41-48-го разрядов регистра младших разрядов сохраняется гасится".


Вот он, момент истины! Очевидно, в арифметику СВС было внесено позднее изменение. Возможно, оно было проведено также и в поздних экземплярах БЭСМ-6. И последняя версия дубненского Алгола про это в курсе. А диспаковский алгол, как и весь остальной софт, вероятно был доработан игнорировать биты порядка в РМР, для совместимости с ранними экземплярами БЭСМ-6.

Правим вышеприведённую строчку в эмуляторе: обнуляем биты порядка в РМР. Снова запускаем алгольный пример со строками:
$ dubna algol.dub
...
ABRA CADABRA
R
BRA CAD

Всё работает в лучшем виде. Репутация Алгола-ГДР восстановлена. Мы набрели на необычный момент истории архитектуры БЭСМ-6.

Date: 2024-07-27 11:25 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Ой какой кошмар...

Date: 2024-07-27 14:05 (UTC)
spamsink: (Default)
From: [personal profile] spamsink
Это еще не кошмар. Кошмар был бы (или ещё будет), если найдётся программа, которая существенно зависит от "старого" варианта ISA.

Кстати говоря, ISA PDP-11 изобилует тонкими различиями в выполнении некоторых команд: в частности связанными с автоинкрементом/декрементом.

Date: 2024-07-27 13:59 (UTC)
spamsink: (Default)
From: [personal profile] spamsink
Старшие разряды РМР образовались не от умножения, а от чего-то, что было даже ещё раньше вычитания, и в цитату трассы не попало. Это, видимо, было какое-нибудь НТЖ (aex).