Спустя полтора года, завеса тайны все-таки начала спадать!
Во-первых, благодаря
вот этому совету
Дмитрия Чернова, баг удалось локализовать. А именно, Дмитрий предположил, что проблему надо искать в
контексте x87 FPU, и что добавление
пары asm- командПрямую вставку asm- команд в код мой фортран-компилятор не умеет, но все необходимое делает ключ Qfp-stack-check
в подозрительных местах приведет к вылету программы по Access Violation именно
в том месте, где что-то пошло не так. А не спустя какое-то время, когда я снова полезу в FPU и получу Nan, например, в ГСЧ. Эта идея сработала, и я получил Access Violation в совершенно безопасной (как мне казалось)
функции SUBROUTINE SCREEN_PUTL0_TIME(TEXT)
USE ABD_INC; USE HEADERS
character, intent(in) :: text*(*)
integer*4, save :: isw=0
c
c При самом первом вызове таймера isw=0, функция вернет 0
c При последующих (isw=1) - вернет интервал от момента инициализации
t=timer_mm(5,isw)
isw=1
if (t < $Screen_counter_time) return
c В крайнем случае (если в момент начала внешнего цикла таймер уже
c инициализирован) функция первый раз напечатает % сразу при старте,
c а не через $Screen_counter_time после запуска цикла
c
call screen_putl0(text)
t=timer_mm(5,0) ! Реинициализация таймера после печати строки
end
Эта функция печатает на экран % выполнения (он передается в строке TEXT), но с интервалом не менее $Screen_counter_time. Для проверки времени, прошедшего с прошлой печати, вызывается самодельный таймер
t=timer_mm(5,isw)
Первый параметр функции - это номер таймера (их там у меня целый массив для разных нужд). А второй параметр работает так: если isw=0, то таймер засекает время, а в остальных случаях возвращает число секунд, прошедших с момента инициализации счетчика. Ну вот так было когда-то сделано, чтобы обойтись одной функцией вместо двух....
Таким образом, когда я дергаю инициализацию таймера, то его возвращаемое значение меня не интересует. Именно это и происходит в предпоследней строке кода выше:
t=timer_mm(5,0)
Результат выполнения функции как бы присваивается переменной t, но больше она нигде
не используется . Как оказалось, именно здесь и была зарыта собака.
А дальше уже было проще. В коде под спойлером у меня есть вызов функции типа real*4, от которого мне был нужен только побочный эффект (инициализация таймера), а вот возвращаемое значение функции нигде не используется. В принципе,
это легальноПо идее, компилятор в такой ситуации должен после вызова функции восстановить стек x87 FPU, а возвращаемое значение никуда не копировать. В других местах кода у меня есть аналогичные вызовы (когда возвращаемое значение не используется), и это не приводит к каким-то багам. Ну и язык официально нигде не требует, чтобы возвращаемое значение функции обязательно было куда-то использовано ;-)
Но как оказалось, именно это и провоцировало проблему. Этот фрагмент библиотеки у меня состоит из кучи очень небольших (5-10 строк) взаимосвязанных функций с частично повторяющимся кодом. Оптимизатор делал из них жуткое спагетти, дробя эти функции на еще более мелкие фрагменты и инлайня их направо и налево. И, видимо, где-то в ходе этих оптимизаций он "забывал" восстановить (очистить?) стек FPU.
В общем, для исправления бага оказалось достаточно заменить локальную переменную t на глобальную. Про нее оптимизатор не знает - будет ли она нужна, или нет. Поэтому он просто вынужден извлекать из сопроцессора результат FP-вычисления, чтобы запихнуть его в
это место ;-)
Огромное спасибо Дмитрию, который сначала выдвинул правильную версию происхождения бага, а потом помог его точно локализовать и убрать! Тестовая программа работает уже час и пока ни одного Nan-а не появилось. ;-)