Уважаемые товарищи!
Я нашёл ошибку в компиляторе OO2C, прошу вас помочь мне её устранить. Уж извините, что так много написал.
Но сначала немного о себе (это к ошибке не особо относится).
Оберон я открыл для себя около года назад. За это время я немало написал на Обеорне, ощутил достоинства этого языка, составил учебную программу обучения программированию на основе Оберона, через неё уже успешно прошло 4 человека.
Сначала я использовал XDS, но пришлось от него отказаться, потому что он не работал на некоторых системах, затем - VOC. От него тоже пришлось отказаться, потому что я не смог портировать его на Windows без Cygwin, затем я стал использовать OO2C (большое спасибо Алексею Ильину за то, что он освежил этот проект несколько лет назад). Хочу сказать, что на всём протяжении освоения Оберона я пытался использовать и Blackbox, но он отталкивает своей излишней привязанностью к Windows, так что с ним пока не получилось.
Теперь об ошибке в OO2C.Писал я себе спокойно программу, как вдруг началась какая-то чертовщина - добавляешь в текст процедуры Out.Int(i, 0); и процедура начинает работать совсем по-другому. Изучив генерирующиеся си-файлы я понял, что это какая-то ошибка оптимизации в компиляторе. Дальше встала задача выдрать этот кусок, но так, чтобы «не повредить» ошибку. Оказалось, что это не так просто - достаточно убрать что-то лишнее и всё внезапно начинает работать правильно, но в итоге получилось. Вот код модуля (55 строк):
Код:
MODULE BugFix;
IMPORT Out, SYSTEM;
TYPE
Bitmap = POINTER TO BitmapDesc;
BitmapDesc = RECORD
w, h: LONGINT;
surface: BOOLEAN
END;
VAR B: Bitmap;
PROCEDURE PutPixelQuick*(x, y: LONGINT);
BEGIN
IF x >= 0 THEN
Out.String(' PUT'); Out.Int(x, 3); Out.Int(y, 3); Out.Ln
END
END PutPixelQuick;
PROCEDURE Line*(bmp: Bitmap; x1, y1, x2, y2: LONGINT);
VAR x, y, i, dx, dy, sx, sy, e: LONGINT; vert: BOOLEAN;
BEGIN
dx := ABS(x1 - x2); dy := ABS(y1 - y2);
IF x2 > x1 THEN sx := 1 ELSE sx := -1 END;
IF y2 > y1 THEN sy := 1 ELSE sy := -1 END;
x := x1; y := y1; vert := dy > dx;
IF vert THEN i := dx; dx := dy; dy := i END;
e := 2 * dy - dx;
(*Out.Bool(vert);*) (* Magically fixes everything *)
FOR i := 0 TO dx DO
(*Out.Int(i, 2); Out.String('=i ');*) (* Makes 6-4 / 7-3 *)
(* Removing "bmp.w" and "bmp.h" checks here makes all correct.
Changing "bmp.*" to "100" or to constants or to global
variables magically fixes everything too. *)
IF (x >= 0) & (y >= 0) &
(x < bmp.w) & (y < bmp.h) THEN
PutPixelQuick(x, y)
END;
IF e >= 0 THEN
IF vert THEN INC(x, sx) ELSE INC(y, sy) END;
DEC(e, 2 * dx)
END;
IF vert THEN INC(y, sy) ELSE INC(x, sx) END;
INC(e, 2 * dy)
END
END Line;
BEGIN
NEW(B); B.w := 100; B.h := 100;
Out.String('LINE (0; 0) TO (7; 7):'); Out.Ln;
Line(B, 0, 0, 7, 7); Out.Ln;
Out.String('LINE (0; 0) TO (3; 7):'); Out.Ln;
Line(B, 0, 0, 3, 7); Out.Ln;
Out.String('LINE (0; 0) TO (7; 3):'); Out.Ln;
Line(B, 0, 0, 7, 3)
END BugFix.
Ошибка происходит при компиляции процедуры Line.
Назначение процедуры - отрисовать отрезок из точки (x1; y1) в (x2; y2).
Пояснения:
1. Отрезок всегда рисуется из точки №1 в точку №2, какая из них где бы ни находилась.
2. dx и dy - это расстояния (неотрицательные) между данными точками по X и по Y соответственно.
3. sx и sy - это направление (-1 или +1) движения от точки №1 к точке №2 (например, если точка №1 левее, то sx = 1, иначе sx = -1)
4. x и y - это точка «на которой мы сейчас находимся», проходя по прорисовываемому отрезку.
4. vert - это булевская переменная, которая истинна в случае, если наклонный отрезок более вытянут по вертикали, чем по горизонтали.
5. В случае, если vert - истина, отрезок следует рисовать, пробегая его сверху вниз (или снизу вверх), а не слева направо, чтобы не было разрывов между точками. Однако в этой реализации алгоритма данный нюанс реализован таким образом: во-первых, перед циклом, в случае если vert - истина, переменные dx и dy меняются местами, а во-вторых, внутри цикла (в двух местах) в зависимости от значения vert, изменяется значение либо x, либо y:
Код:
IF vert THEN INC(x, sx) ELSE INC(y, sy) END;
5. В нескольких местах можно заметить, что dx или dy умножается на 2. Это делается для того, чтобы можно было обойтись без деления пополам (чтобы избежать округления).
Если скомпилировать данный модуль с помощью OO2C (oo2c -M BugFix), то отрезок из (0; 0) в (7; 3) будет отрисован неправильно:
Код:
PUT 0 0
PUT 1 0
PUT 2 1
PUT 2 2
PUT 3 3
PUT 3 4
PUT 4 5
PUT 4 6
Во первых, он явно идёт в точку (3; 7), а не (7; 3), а во-вторых, он топчется два шага на нуле и в результате доходит только до (4; 6). (На самом деле, дело не в том, что он топчется на нуле, там вообще как-то по-странному всё сбивается.)
Дёргаем ошибку за разные места:1. Если раскомментировать эту строчку:
Код:
(*Out.Bool(vert);*) (* Magically fixes everything *)
то происходит собственно чертовщина, всё начинает работать как часы.
2. Если закомментировать её обратно, но раскомментировать эту:
Код:
(*Out.Int(i, 2); Out.String('=i ');*) (* Makes 6-4 / 7-3 *)
то ошибка превращается в другую. Теперь проблемный отрезок рисуется правильно, зато другой портится - он строится не в (3; 7), как ему было сказано, а в (6; 4). То есть ошибка типа симметричная, но какого чёрта? мы всего-навсего вывели на экран содержимое переменной "i".
3. Если закомментировать всё обратно, но убрать из IF'а эту часть условия:
Код:
(x < bmp.w) & (y < bmp.h)
то всё опять работает.
4.
И самая эпика. Если вернуть проверку из предыдущего пункта обратно, но только вместо bmp.w и bmp.h использовать две обычные глобальные переменные (заранее проинициализированные в главном BEGIN'е)...
Код:
VAR bmpw, bmph: LONGINT;
...
... & (x < bmpw) & (y < bmph) THEN ...
то ошибка тоже пропадает! Если использовать константные значения или ещё что-то, но ошибки не будет. Она появляется только если w и h находятся внутри POINTER TO RECORD.
А такой классный компилятор. Только теперь, получается, ошибку может выдать в любой программе в неожиданном месте. Что теперь с ним делать? Я искал как в нём отключить оптимизацию или что-нибудь, но не факт, что это возможно.
Генирируемый код на Си выглядит довольно сложно. У него там
register OOC_INT32 i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12;
а потом бессмысленная чехарда с регистрами по туда-сюда.
BugFix.c модуля, приведённого выше:
Код:
#include <BugFix.d>
#include <__oo2c.h>
#include <setjmp.h>
void BugFix__PutPixelQuick(OOC_INT32 x, OOC_INT32 y) {
register OOC_INT32 i0,i1;
i0 = x;
i1 = i0>=0;
if (!i1) goto l4;
Out__String((OOC_CHAR8*)" PUT", 6);
Out__Int(i0, 3);
i0 = y;
Out__Int(i0, 3);
Out__Ln();
l4:
return;
;
}
void BugFix__Line(BugFix__Bitmap bmp, OOC_INT32 x1, OOC_INT32 y1, OOC_INT32 x2, OOC_INT32 y2) {
register OOC_INT32 i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12;
OOC_INT32 dx;
OOC_INT32 dy;
OOC_INT32 sx;
OOC_INT32 sy;
OOC_INT32 x;
OOC_INT32 y;
OOC_CHAR8 vert;
OOC_INT32 i;
OOC_INT32 e;
i0 = x1;
i1 = x2;
i2 = y2;
i3 = y1;
i4 = _abs((i0-i1));
dx = i4;
i5 = _abs((i3-i2));
dy = i5;
i1 = i1>i0;
if (i1) goto l3;
sx = (-1);
i1=(-1);
goto l4;
l3:
sx = 1;
i1=1;
l4:
i2 = i2>i3;
if (i2) goto l7;
sy = (-1);
i2=(-1);
goto l8;
l7:
sy = 1;
i2=1;
l8:
x = i0;
y = i3;
vert = (i5>i4);
if ((i5>i4)) goto l11;
i6=i5;i7=i4;
goto l12;
l11:
dx = i5;
dy = i4;
i6=i4;i7=i5;
l12:
i6 = 2*i6;
i8 = i6-i7;
e = i8;
i = 0;
i9 = 0<=i7;
if (!i9) goto l46;
i9 = 2*i7;
i10 = (OOC_INT32)bmp;
i11=0;
l15_loop:
i12 = i0>=0;
if (i12) goto l18;
i12=0u;
goto l20;
l18:
i12 = i3>=0;
l20:
if (i12) goto l22;
i12=0u;
goto l24;
l22:
i12 = *(OOC_INT32*)(_check_pointer(i10, 1066));
i12 = i0<i12;
l24:
if (i12) goto l26;
i12=0u;
goto l28;
l26:
i12 = *(OOC_INT32*)((_check_pointer(i10, 1080))+4);
i12 = i3<i12;
l28:
if (!i12) goto l30;
BugFix__PutPixelQuick(i0, i3);
l30:
i12 = i8>=0;
if (!i12) goto l38;
if ((i5>i4)) goto l35;
i3 = i3+i2;
y = i3;
goto l36;
l35:
i0 = i0+i1;
x = i0;
l36:
i8 = i8-i9;
l38:
if ((i5>i4)) goto l40;
i0 = i0+i1;
x = i0;
goto l41;
l40:
i3 = i3+i2;
y = i3;
l41:
i4 = i8+i6;
e = i4;
i5 = i11+1;
i = i5;
i8 = i5<=i7;
if (!i8) goto l46;
i8=i4;i11=i5;
goto l15_loop;
l46:
return;
;
}
void OOC_BugFix_init(void) {
register OOC_INT32 i0;
i0 = (OOC_INT32)RT0__NewObject(_td_BugFix__Bitmap.baseTypes[0]);
BugFix__B = (BugFix__Bitmap)i0;
*(OOC_INT32*)(_check_pointer(i0, 1339)) = 100;
*(OOC_INT32*)((_check_pointer(i0, 1351))+4) = 100;
Out__String((OOC_CHAR8*)"LINE (0; 0) TO (7; 7):", 23);
Out__Ln();
i0 = (OOC_INT32)BugFix__B;
BugFix__Line((BugFix__Bitmap)i0, 0, 0, 7, 7);
Out__Ln();
Out__String((OOC_CHAR8*)"LINE (0; 0) TO (3; 7):", 23);
Out__Ln();
i0 = (OOC_INT32)BugFix__B;
BugFix__Line((BugFix__Bitmap)i0, 0, 0, 3, 7);
Out__Ln();
Out__String((OOC_CHAR8*)"LINE (0; 0) TO (7; 3):", 23);
Out__Ln();
i0 = (OOC_INT32)BugFix__B;
BugFix__Line((BugFix__Bitmap)i0, 0, 0, 7, 3);
return;
;
}
void OOC_BugFix_destroy(void) {
}
/* --- */
Спасибо за внимание.