временное решение не то чтобы работает… но виновник идентифицирован правильно.
что, тащемта, происходит: модуль Destore заведует во-первых, уничтожением лишних LOAD/STORE, а во-вторых, он делает интересную оптимизацию: заменяет некоторые загрузки из регистров на прямое использование выражений. в нашем случае заменяет `vert` на то самое странное `iA > iB`. это снижает нагрузку на register allocator (хотя зачем такие танцы для сишного бэкэнда — неясно).
проблема, однако, в том, что `vert` 1) находится в цикле, и 2) является для тела цикла инвариантом. после замены оптимизатор вполне логично видит, что `vert` дальше по коду нигде не используется, но не понимает, что вместо неё используются `iA` и `iB`. поэтому он не помечает их как «использованые в теле цикла», и регистровый аллокатор радостно использует их повторно для изменения индекса FOR.
правильный фикс будет, соответственно, примерно такой: если мы в цикле, и заменили переменную на выражение, то пометить регистры этого выражения как используемые (добавить в `useList`, я полагаю). но я не разбирался в SSA-части компилятора, не очень понимаю, что там куда, мне страшно, и у меня лапки. поэтому я пошёл более простым путём: просто отломал почти весь Destore, путем замены функции `SameInstr()` на заглушку, которая всегда возвращает FALSE. это оторвало замену переменной на значение, и компилятор был вынужден засунуть её в регистр, и сохранять этот регистр живым.
почему надо именно POINTER TO RECORD, чтобы триггернуть баг? потому что такой код, похоже, создаёт очень удачный паттерн нагрузки на регистровый аллокатор (который, похоже, зачем-то имеет лимит на количество регистров).
вот минимизированый тест для бага:
Код:
MODULE BugFix;
IMPORT Out;
TYPE
Bitmap = POINTER TO BitmapDesc;
BitmapDesc = RECORD
h: LONGINT;
END;
VAR B: Bitmap;
PROCEDURE Line*(bmp: Bitmap; x1, y1, y2: LONGINT);
VAR y, i, sy: LONGINT; vert: BOOLEAN;
BEGIN
IF y2 > y1 THEN sy := 1 ELSE sy := -1 END;
y := y1; vert := y1 < x1;
(* either FOR, or WHILE will trigger the bug. FOR seems to be converted to this WHILE anyway. *)
i := 0;
WHILE i <= y2 DO
(* Removing "bmp.h" check here makes all correct.
It seems that the problem is in pointer dereferencing. *)
IF (y < bmp.h) THEN
(* no need to call anything here *)
END;
IF vert THEN INC(y, sy) END;
i := i+1;
END;
(* should output "1", but outputs "3" *)
Out.Int(y, 3); Out.Ln
END Line;
BEGIN
NEW(B); B.h := 100;
Line(B, 0, 1, 3)
END BugFix.
как написано в тексте, правильный компилятор скажет "1", а неправильный — "3" (потому что попортит один из нужных регистров).
побочный эффект оторваного Destore — исчерпание свободных регистров в аллокаторе, вследствие чего аллокатор генерирует абсолютно бесполезные сдвиги значений между регистрами (похоже, это он так делает spill). в принципе — совершенно некритично, потому что gcc это всё уберёт.