Оберон-клуб «ВЄДАsoft»

Твердыня модульных языков
Текущее время: 27 апр 2024, 09:27

Часовой пояс: UTC + 2 часа




Начать новую тему Ответить на тему  [ Сообщений: 72 ]  На страницу Пред.  1, 2, 3, 4, 5, 6, 7, 8  След.
Автор Сообщение
СообщениеДобавлено: 27 май 2013, 19:45 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
Saferoll писал(а):
Далее меня смутила сама необходимость такого фикса. Ведь если это недоработано здесь, то может быть потребует изменений где-то еще?
Наши типы, которые мы используем, и их разрядность — вобщем-то только для скомпилированного Офронтом кода. Так что если в этом коде вылезут бока — мы об этом узнаем. :) Хотя вообще конечно приходится маленько править (в библиотечных модулях) код Темпла, который рассчитан под 16-битный INTEGER.

Признаюсь тебе честно, не очень люблю когда Оберон-исходники пестрят использованием там и сям типа LONGINT, чем грешат ETH Oberon и AOS. В контексте Windows тип LONGINT у меня уже прочно ассоциировался с 64-мя битами, а INTEGER должен быть основным числовым типом, а значит самым часто используемым. Т.е. он должен быть фактически привязан к разрядности платформы. Чего мы для Спектрума не придерживаемся, просто в силу удобства. Но, кстати, SDCC имеет базовую поддержку и 64-битных вычислений. Что можно будет нам тоже использовать, если понадобится.

Saferoll писал(а):
Вдруг в каком-то месте Ofront существенно используется однобайтовость SHORTINT? Вот это тоже меня смущает.
Пока что проблем не было. Но написал Дж.Темплу письмо, спросил об этом моменте. Ответа пока нет.

Saferoll писал(а):
Я же написал node^.conval^.intval := SHORT(SHORT (v )); что работает, только если соотношение типов 8-16-32.
Сдаётся мне, что node^.conval^.intval для наших целей просто обязан быть типа LONGINT.

Saferoll писал(а):
Т.е. написали в файле параметров SHORTINT=INT8 – стал SHORTINT однобайтовым, написали SHORTINT=INT16 – стал в 2 раза шире. А компилятор работает с INT8 или INT16, а до имени SHORTINT ему и дела нет.
То, что ты предлагаешь, уже реализовано с помощью конфигурационного файла Ofront.par. Проецирование обероновских типов на сишные задано в SYSTEM.h, и мы тоже можем варьировать, например, для ZXDev я описываю SHORTINT как signed char (это единственный байтовый знаковый тип, который можно использовать), а для WinDev он описан как двухбайтовый signed short int. А если будет нужда указать и использовать действительно фактическую разрядность — мы можем задать эти типы (INT8, INT16, INT32, INT64) в стандартном для XDev модуле Platform.Mod, реализация которого будет для каждой платформы своя. Можно конечно задать их и в псевдомодуле SYSTEM, но мне кажется, что средствами самого языка сделать это будет более правильно. Хотя конечно потребуется согласовывать типы и в Ofront.par, и в SYSTEM.h, и в Platform.Mod, — чтобы было соответствие. Такое решение придаёт неплохую гибкость разработке, позволяя использовать в своих программах 3 полноценных числовых типа произвольно заданной разрядности и один вспомогательный (SYSTEM.BYTE), а также использовать явно заданную разрядность типов в тех случаях, где это нужно, оставаясь в рамках стандарта Оберона.

Saferoll писал(а):
Вот как выполнять SHORT(INT32)? Должен тип на 1 ступеньку меньше получиться, а это не обязательно INT16, может быть INT32, а может и INT8.
А знаешь как выглядит SHORT() в сишном исходнике, сгенерированном Ofront'ом? :) А никак он там не выглядит! Ты можешь написать a := SHORT(SHORT(SHORT(b))), а сгенерится a = b; т.е. Си-компилятор будет сам решать насколько обрезать разрядность при этом присваивании. Исходя из типов переменных, т.е. из их разрядности. И если эти переменные одинакового типа, то никакого обрезания разрядности не будет производиться.

Saferoll писал(а):
Вот придется опираться, придется вместо X:=SHORT(Y) писать IF OPM.IntSize=2*OPM.SIntSize THEN X:=SHORT(Y) ELSE ..., потому, что типы Х и Y заранее известны, но неизвестны текущие разрядности этих типов в файле параметров.
Не понял смысл такого усложнения кода. Буду рад более подробному объяснению. :)

Saferoll писал(а):
К сожалению, это будет работать только для 1 и 2 байтовых типов. Для 4-байтового возможно переполнение при вычислении количества итераций. Будем думать.
А если node^.conval^.intval будет у нас типа LONGINT, то никакие SHORT при присвоении ему других типов там и не понадобятся?

Saferoll писал(а):
Ввести еще 1 константу типа и переменные для ее границ тоже не сложно, задать conval^.intval:LONGINT тоже. Но вот что-то не работает LONGINT в ББ. Т.е. он вроде бы и есть, но вот пользоваться им так свободно, как прочими типами не получается - срабатывает ASSERT(reg IN WReg) в компиляторе ББ. Если уж в самом ББ 64-разрядность ограничена, то вряд ли нам удастся так легко ввести ее в Ofront.
Олег, а ты мог бы воспроизвести данную ошибку на маленьком фрагменте кода? Попробуем разобраться. А то на OberonCore и в рассылке по ББ что-то никто не жаловался на глючную работу LONGINT. Так что может просто LONG пропущен, или пытаемся присвоить длинное короткому, а вместо правильной обработки этой ошибки и красивого сообщения — срабатывает кривой трап в виде ASSERT'а (я уже с таким сталкивался).

Saferoll писал(а):
Попытка перейти к типу большей разрядности при помощи LONG() вызывало ТРАП 0 (Dev)CPC486 компилятора ББ.
Сейчас удалось обойти эту особенность компилятора ББ при помощи вспомогательной переменной L: LONGINT, описание которой добавил в начало процедуры OPP.StatSeq
Плиз, не делай таких заплаток, или делай, но только временно. Такие проблемы с LONGINT надо решить кардинально. :) Так что маленький фрагмент кода, воспроизводящий ошибку, — и пишем в рассылку по ББ, вдруг уже есть фикс. Полюбому помогут, там масса грамотных инженеров и учёных, заинтересованных в корректной работе ББ.


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 28 май 2013, 17:32 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Zorko писал(а):
Saferoll писал(а):
Попытка перейти к типу большей разрядности при помощи LONG() вызывало ТРАП 0 (Dev)CPC486 компилятора ББ.
Сейчас удалось обойти эту особенность компилятора ББ при помощи вспомогательной переменной L: LONGINT, описание которой добавил в начало процедуры OPP.StatSeq
Плиз, не делай таких заплаток, или делай, но только временно. Такие проблемы с LONGINT надо решить кардинально. :) Так что маленький фрагмент кода, воспроизводящий ошибку, — и пишем в рассылку по ББ, вдруг уже есть фикс. Полюбому помогут, там масса грамотных инженеров и учёных, заинтересованных в корректной работе ББ.
Ну почему же не делать, если я делаю и сюда пишу? :) Тем более, что это не "заплатка", это альтернативный способ, который делает то же самое, но работает верно (не одно выражение с двумя операциями, а 2 отдельных присваивания). И если ошибку исправят, то продолжит верно работать. Хотя не могу понять, почему нельзя сразу разделить одно поле на другое, даже если первое привели к большей разрядности? Какую-то роль тут указатель играет, потому что без указателя такой ошибки нет.
Вот код, который вызывает такие проблемы. Самый минимальный, какой смог найти (если не указатель на запись, то ошибки нет; если не DIV , а MOD или + , то тоже всё нормально; если LONG не делать или тип поля меньше ,чем INTEGER, то тоже ТРАП не вылезает).
Код: "OBERON"
  1. (* BlackBox Component Builder 1.6-rc6 Windows XP *)
  2. MODULE LongintDIV;
  3.  
  4. TYPE
  5. (* Object = RECORD int: INTEGER END ; таких проблем не вызывает *)
  6. Object = POINTER TO RECORD int: INTEGER END ;
  7. VAR
  8. u : Object;
  9. L : LONGINT;
  10. BEGIN
  11. NEW(u); (* это можно пропустить, на TRAP 0 не влияет *)
  12. u.int :=1; (* можно и не присваивать, на TRAP 0 не влияет *)
  13. L := LONG(u.int) DIV u.int ; (* вот это вызывает TRAP 0 *)
  14. (* Если написать L := LONG(u.int); L := L DIV u.int; то компилируется нормально *)
  15. END LongintDIV.
  16.  
  17. При компиляции выдает
  18. TRAP 0
  19.  
  20. DevCPC486.CheckAv [00000526H]
  21. .reg INTEGER 0
  22. DevCPC486.Floor [00002E20H]
  23. .c DevCPL486.Item fields
  24. .local INTEGER 2288212
  25. .useSt1 BOOLEAN FALSE
  26. .x DevCPL486.Item fields
  27. DevCPC486.FloatDOp [00004708H]
  28. .a DevCPL486.Item fields
  29. .b DevCPL486.Item fields
  30. .local INTEGER 0
  31. .rev BOOLEAN FALSE
  32. .subcl BYTE 3
  33. .x DevCPL486.Item fields
  34. .y DevCPL486.Item fields

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 28 май 2013, 17:46 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
Отлично, Олег. :) Этот фрагмент кода я запостил в рассылку по ББ, посмотрим, что ответят.

Тем временем есть ответ от Джозефа:
Josef Templ писал(а):
Dear Oleg,

I don't remember the details of the integer type sizes.
So I have to look it up in the code, which I cannot do until the next week.
Please remind me if a forget to come back with it.

I guess, however, that the ETH Oberon systems will not compile/run with *INT* type sizes being changed. It is alos the question of the
runtime system will work as expected.

- Josef

Ну вот. А runtime system мы перепишем. И если возникнут какие-то косяки, то лучше нам узнать об этом как можно раньше, так что будем держаться того, что видится правильным в перспективе.


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 28 май 2013, 19:32 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
Да, проблема с LONGINT DIV оказалась известной, и патч для неё, оказывается, есть. :)
В рассылке по ББ есть один чувак китаец luowy, дюже умный! Такие патчи сам делает на ура. :)
Так что пользуемся. Вот коммит, реализующий данный патч.


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 28 май 2013, 20:08 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Zorko писал(а):
Да, проблема с LONGINT DIV оказалась известной, и патч для неё, оказывается, есть. :)
В рассылке по ББ есть один чувак китаец luowy, дюже умный! Такие патчи сам делает на ура. :)
Так что пользуемся. Вот коммит, реализующий данный патч.
Вот и хорошо. Патч поставлю, но переменную L:LONGINT оставлю всё равно - в ней удобно хранить количество итераций. Одной системой флажков-признаков через имеющиеся переменные не обойдешься, особенно если будем переходить на 64 бита.

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 29 май 2013, 20:40 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Zorko писал(а):
Признаюсь тебе честно, не очень люблю когда Оберон-исходники пестрят использованием там и сям типа LONGINT, чем грешат ETH Oberon и AOS. В контексте Windows тип LONGINT у меня уже прочно ассоциировался с 64-мя битами, а INTEGER должен быть основным числовым типом, а значит самым часто используемым. Т.е. он должен быть фактически привязан к разрядности платформы. Чего мы для Спектрума не придерживаемся, просто в силу удобства. Но, кстати, SDCC имеет базовую поддержку и 64-битных вычислений. Что можно будет нам тоже использовать, если понадобится.
Что такое целые типы Оберона и привязывать ли их к разрядности – тема, достойная отдельной ветки. Если кратко, то думаю так. Типы SHORTINT, INTEGER, LONGINT не привязаны к конкретной разрядности в описании языка. И это правильно, потому что описание задает ЯП как формальную систему. Так в описание нет также ограничений на длину идентификатора, уровень вложенности IF и т.д. Но конкретные программы пишутся на конкретных компиляторах, где есть и максимальная длина идентификаторов и вложенности и конкретные привязки типов к разрядности. А программист, когда пишет, обязательно подразумевает какую-то конкретную разрядность. А иначе как, из каких соображений, он может выбрать SHORTINT, INTEGER или LONGINT вот для этой переменной? Только сравнивая число возможных значений из предметной области с диапазоном значений для данного типа.
При этом INTEGER действительно имеет смысл «нормальный целый тип» и нужно использовать обычно его. LONGINT - «большой тип», его следует использовать, когда INTEGER не хватает. SHORTINT означает «маленький тип» и обычно используется, когда диапазон значений заведомо мал и нужно сэкономить память.
А если программируют что-то «системное» (вот как мы здесь), то и типы выбирают соответственно, но тогда тем более есть конкретная привязка к разрядности. Вот я сейчас пишу реализацию FOR, считая что тип ББ INTEGER имеет 4 байта, а тип LONGINT 8. И никак иначе не получается, "сферическая абстракция в вакууме" так в "вакууме" и останется, на реальном железе надо что-то поконкретнее. :)
Не видел исходники ETH Oberon и AOS, но может там использование LONGINT оправдано? Так я, например, ввел L: LONGINT, чтобы оперировать с величиной B-A (A,B: INTEGER), потому что разность или сумма двух таких величин в сам тип не влазит. Может и там для чего-то подобного нужно.
Цитата:
Сдаётся мне, что node^.conval^.intval для наших целей просто обязан быть типа LONGINT.
Если хотим ввести 64-разрядность, то да. Правда мне придется опять переделывать реализацию FOR и существенно. Потому что сейчас я обошел переполнение B-A приведением к LONGINT. А если А,В сами будут LONGINT? 128-битной арифметики нет, придется как-то по-другому. Вообще-то придумал уже как, просто сложнее немного.
Цитата:
То, что ты предлагаешь, уже реализовано с помощью конфигурационного файла Ofront.par. ...Такое решение придаёт неплохую гибкость разработке, позволяя использовать в своих программах 3 полноценных числовых типа произвольно заданной разрядности и один вспомогательный (SYSTEM.BYTE), а также использовать явно заданную разрядность типов в тех случаях, где это нужно, оставаясь в рамках стандарта Оберона.
Гибкость – это хорошо, но не следует ей злоупотреблять. Ранее я писал, что по стандарту (как я его понимаю) целые типы могут совпадать друг с другом. Но следует ли задавать такое совпадение в конфигурации? Ведь для чего-то программист использует другой тип, а не этот (например для контроля за переполнением, как я L). А если вдруг типы совпадут, то никакой обработки переполнения не произойдет. Получится, что программа работает неверно. Так что, я бы считал, что «SHORTINT строго меньше INTEGER, который, в свою очередь, строго меньше LONGINT».
Цитата:
А знаешь как выглядит SHORT() в сишном исходнике, сгенерированном Ofront'ом? :) А никак он там не выглядит! Ты можешь написать a := SHORT(SHORT(SHORT(b))), а сгенерится a = b; т.е. Си-компилятор будет сам решать насколько обрезать разрядность при этом присваивании. Исходя из типов переменных, т.е. из их разрядности. И если эти переменные одинакового типа, то никакого обрезания разрядности не будет производиться.
Цитата:
Saferoll писал(а):
Вот придется опираться, придется вместо X:=SHORT(Y) писать IF OPM.IntSize=2*OPM.SIntSize THEN X:=SHORT(Y) ELSE ..., потому, что типы Х и Y заранее известны, но неизвестны текущие разрядности этих типов в файле параметров.
Не понял смысл такого усложнения кода. Буду рад более подробному объяснению. :)
Чтобы не было путаницы, нужно ввести специальные термины. Ofront – это программа на Обероне, работающая в среде BlackBox. В этой программе используются типы BlackBox-а, и разные операции над переменными, имеющими этот тип (назовем это "типы и операции ББ"). Одновременно Ofront является транслятором программ на Оберон 2, в которых можно использовать разные типы и операции ("типы и операции Ofront").
Типы Ofront'a отображаются в типы Си, операции Ofront'a тоже как-то выражаются через операции Си. Для этого транслятор Ofront моделирует типы, прочитанные им в программе, через свои переменные (которые имеют тип ББ). Так целые константы моделируются в узле через поля:
    node^.conval^.intval типа INTEGER (4 –байтовый тип ББ) – значение константы, и
    node^.typ – тип константы.
Поскольку выбран тип ББ INTEGER, то можно представить любой целый тип Ofront в этом узле (сейчас целые типы Ofront не превышают 32-разряда). Если необходимо произвести какие-то манипуляции с узлом дерева для своих собственных нужд (не для трансляции этих манипуляций в Си), то Ofront должен ориентироваться на значения переменных OPM.SIntSize, OPM.MinSInt и OPM.MaxSInt и т.д. Потому что заранее неизвестно, что будет написано в файле конфигурации Ofront.par.
Так что операции SHORT и LONG, которые читает Ofront в текущем окне ( и должен транслировать в Си) может и не требуют такого усложнения, потому что с ними компилятор Си будет разбираться. А вот если эту операцию Ofront для собственных нужд делает, то он и должен соотношение разрядности учитывать, потому что больше некому.
Я делал вычисление количества повторений цикла FOR. Это количество выражается в общем случае беззнаковой константой, в знаковый тип не влезает. Но для тех чисел что не влезают, можно подобрать отрицательное значение, которое из-за переполнения сработает нужным нам образом. Например, в однобайтовом знаковом типе нет значения 130, но можно использовать «-126», вычитая из которого 1 можно дойти до нуля (-127, -128,127,126, …1,0) за 130 шагов, что и требуется. Для вычисления этой константы Ofront должен производить усечение SHORT поля node^.conval^.intval до нужной разрядности. А поскольку разрядность задается переменными, то необходимо выполнить SHORT либо 1 раз, либо 2 раза в зависимости от параметров Ofront.par (а если 64 бита введем, то и появится еще вариант с тремя SHORT).
Цитата:
Saferoll писал(а):
К сожалению, это будет работать только для 1 и 2 байтовых типов. Для 4-байтового возможно переполнение при вычислении количества итераций. Будем думать.
А если node^.conval^.intval будет у нас типа LONGINT, то никакие SHORT при присвоении ему других типов там и не понадобятся?
Понадобятся, еще больше SHORT-ов понадобится! Почему - см выше.
Кстати, вот сейчас увидел, что не совсем верно написал учет размерности типов в SetUnsignedType. Надо не IF 4*OPM.SIntSize = OPM.LIntSize писать, а IF OPM.SIntSize=1 и т.д. Т.е. надо соотносить тип Ofront SHORTINT к типу ББ INTEGER (тип поля node^.conval^.intval), а не к типу Ofront LONGINT, разрядность которого плавает.

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 30 май 2013, 16:57 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
Saferoll писал(а):
Вот я сейчас пишу реализацию FOR, считая что тип ББ INTEGER имеет 4 байта, а тип LONGINT 8. И никак иначе не получается, "сферическая абстракция в вакууме" так в "вакууме" и останется, на реальном железе надо что-то поконкретнее. :)
Н.Вирт в Обероне-07 оставил всего один целочисленный тип INTEGER, что сделало язык малопригодным для работы ИМХО. Но за этим скрывается идея унифицировать взгляд на целые. Т.е. ЦЕЛОЕ, а идеальный компилятор сам уже разберётся, короткое оно или длинное, ибо первое важно для огромных массивов данных, а второе — для точных расчётов с большими числами. Но на практике ещё не встречается компиляторов, которые смогли бы верно распознать длину типа автоматически. Тенденции как раз обратные — всё приводится к одному типу большой разрядности (например, в Python — 64 бита).

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

Кстати, отмечу, что в Си, например, при взгляде на присваивание a = b нельзя заведомо сказать, не зная контекста выполнения и типов переменных, может ли быть здесь потеря разрядности. На Обероне, благодаря его SHORT, — это видно сразу. Притом Вирт, благодаря его концепции "поглощения меньших типов большими, а целых — вещественными", сделал 2 полезных вещи: во-первых, явно видно, где разрядность может быть понижена, а во-вторых не нужно её явно повышать там, где она и так не потеряется. Не нужно явно приводить целые к вещественным (ведь такое приведение осуществляется без потерь). И в то же время, никто не мешает явно повышать разрядность с помощью LONG. Вобщем, придумано явно удачнее, чем в Си.

Когда я активно писал на Си, то пользовался машинным представлением чисел там и сям. Меня не смущали ни заёмы, ни переполнения. Числа — это набор бит, и всё тут. В Си так принятно. Когда же довелось плотно поработать на Обероне — понял всю ценность отношения к числовым типам как к абстрактному представлению чисел. Я понял, что в идеале SHORT не надо использовать для отбрасывания старших битов. Да, ББ и, тем более, Ofront не отловят такое использование SHORT. Но по замыслу SHORT применяется только для того, чтобы сказать: вот обычное целое переходит в короткое целое (или длинное переходит в обычное целое), это волевой акт работы ума программиста, но программист не отбрасывает биты (его на данном этапе даже не интересует как представлено число в памяти машины), он просто явно указывает на понижение мощности типа, что добавляет программе ясности и лёгкости чтения/понимания.

И в идеале конечно если при выполнении SHORT результат усекается, умный рантайм должен обязательно предупредить. Таким образом, программисту становится ценнее каждый бит информации, и его приходится учитывать, а не отбрасывать. Поэтому сейчас я, например, не просто привожу INTEGER к BYTE, а добавляю MOD 256. Код становится немного больше (хотя умный компилер может и такое оптимизировать), но повышается понятность. Снижается зависимость результата вычислений от рамок типа. Так что советую не отсекать старшие биты SHORT'ом, не в этом его соль (Вместе с тем понятно, что при понижении разрядности отрицательных — биты по-любому будут отброшены).

Но это, понятно, в идеале. Что для системного программирования уже не столь существенно, поскольку нужна высокая эффективность.

Saferoll писал(а):
Не видел исходники ETH Oberon и AOS, но может там использование LONGINT оправдано?
Да, конечно оправдано, но, скорее, в силу традиции. ETH Oberon разрабатывался в то время, когда программисты оперировали разрядностью 8-16-32, и удобно было спроецировать систему типов на эти величины. С тех пор как 32-битность стала фактически нормой, востребованность типов 8-16 несколько снизилась, а потребность в 64 выросла. Что было учтено при проектировании BlackBox и, конечно, Ofront. Но Йозеф, сделав некоторые намётки в виде чтения разрядности типов из Ofront.par, всё-таки не довёл эту идею до реализации вполне, да и не особо это было ему нужно, поскольку для его потребностей хватало 32-битного числового типа, и он использовал традиционную в то время систему 8-16-32. Но когда это было? Где-то в 90-х, как показывают даты создания файлов Ofront'а. Это старичок ещё тот. :)

Хватит ли нам 3 + 1 целочисленных типа или придётся всё равно в итоге добавить-таки HUGEINT? Пока что вроде бы хватает, я как-то скептически отношусь к 128-битной разрядности. Если такие процессоры и станут массовыми, оставшиеся 32 и 64 они целиком не потеснят. Адресовать 64-мя битами уже можно дофига памяти, да и расчёты врятли будут переводиться все поголовно под 128. Так что вот тогда-то нам и понадобится, быть может, HUGEINT. А может быть и нет. :)

Saferoll писал(а):
Гибкость – это хорошо, но не следует ей злоупотреблять. Ранее я писал, что по стандарту (как я его понимаю) целые типы могут совпадать друг с другом. Но следует ли задавать такое совпадение в конфигурации? Ведь для чего-то программист использует другой тип, а не этот (например для контроля за переполнением, как я L). А если вдруг типы совпадут, то никакой обработки переполнения не произойдет. Получится, что программа работает неверно. Так что, я бы считал, что «SHORTINT строго меньше INTEGER, который, в свою очередь, строго меньше LONGINT».
Да, я тоже не вижу особого смысла делать эти типы одной разрядности. И хотя сейчас в XDev типы LONGINT и INTEGER 32-битны, однако это временная мера, поскольку LONGINT мы подготавливаем к 64-битности.

Saferoll писал(а):
Если хотим ввести 64-разрядность, то да. Правда мне придется опять переделывать реализацию FOR и существенно. Потому что сейчас я обошел переполнение B-A приведением к LONGINT. А если А,В сами будут LONGINT? 128-битной арифметики нет, придется как-то по-другому. Вообще-то придумал уже как, просто сложнее немного.
Saferoll писал(а):
Я делал вычисление количества повторений цикла FOR. Это количество выражается в общем случае беззнаковой константой, в знаковый тип не влезает. Но для тех чисел что не влезают, можно подобрать отрицательное значение, которое из-за переполнения сработает нужным нам образом. Например, в однобайтовом знаковом типе нет значения 130, но можно использовать «-126», вычитая из которого 1 можно дойти до нуля (-127, -128,127,126, …1,0) за 130 шагов, что и требуется.
Saferoll писал(а):
К сожалению, это будет работать только для 1 и 2 байтовых типов. Для 4-байтового возможно переполнение при вычислении количества итераций. Будем думать.
Олег, нам нужна формула! :)
Возьмём вычисление количества повторений цикла FOR. Если оно может быть свёрнуто в константу (константное выражение), временной мерой может быть использование в этом случае вычислений в рамках типа ББ LONGINT (64 бита). Поскольку пока что в Ofront'е нет поддержки 64 бит, нам хватит диапазона 0..MAX(LONGINT) аж с головой.

Поскольку, опять же, Ofront не поддерживает 64 бита, то можно ограничиться при расчётах типом ББ LONGINT, но результат сохранять (упаковывать SHORT'ом) в тип INTEGER, считая его неотрицательным.

Кстати, только что проверил компилировать в SDCC что-то такое:
Код: "OBERON"
Компилятор показывает warning, но генерирует положенный 0. А на s = 255 даже не ругается, хотя s — тип знаковый.

Когда мы касаемся расчётов количества повторений цикла FOR в нашей программе уже на типах Ofront'а, то здесь есть проблема, как ты верно заметил. Нам нужно записать формулу, которая вычислит а рантайме на типах Ofront'а количество повторений цикла. И вот какой появляется вопрос. Очевидно, что для количества повторов цикла FOR мы используем описанную Ofront'ом для этих целей переменную xxx__for__1, тип которой совпадает с переменной-счётчиком цикла. Понятно, что если интерпретировать эту переменную как беззнаковую, то её диапазона хватит для вмещения количества повторов, что вытекает из того факта, что границы счётчика заданы именно в рамках этого типа. Таким образом, мы лишаемся проблемы сложного вычисления типа этой переменной, перекладывая это на плечи программиста, да так, что он об этом и не узнает.

Когда работает формула вычисления количества повторов — ей не хватает разрядности, и надо прибегнуть к расчётам на более высокой разрядности, как я понял, не так ли? Но тут есть существенная проблема: для максимального типа LONGINT, какой бы разрядности он ни был, уже нету и не может быть более широкого типа с большей разрядностью, так что это тупик. Отсюда появляется 2 решения: либо приводить если не переменную, то хотя бы расчёты к unsigned (средствами Си через макрос, заданный в SYSTEM.h, как ты хотел сделать для возможности реализовать детали цикла FOR на ассемблере), либо же оставаться в рамках знаковых расчётов средствами рантайма для данного типа. Но ни в коем случае не переходить на тип большей разрядности, потому что: a) его точная разрядность нам неизвестна; b) она может оказаться такой же; c) наконец, типа, бОльшего, чем заданный, может не оказаться в нашем ящичке с инструментами. Думаю, этих контраргументов хватит.

Так что нам нужна формула. :) Которая отработает в рамках одного заданного типа. И что-то мне подсказывает, что надо начать с рантайма, а для константных свёрток использовать ту же формулу. При этом нас не ограничивают заём/переполнение, мы можем накрайняк вводить новые макроопределения в SYSTEM.h и т.д.


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 30 май 2013, 21:25 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Zorko писал(а):
Н.Вирт в Обероне-07 оставил всего один целочисленный тип INTEGER, что сделало язык малопригодным для работы ИМХО. Но за этим скрывается идея унифицировать взгляд на целые. Т.е. ЦЕЛОЕ, а идеальный компилятор сам уже разберётся, короткое оно или длинное, ибо первое важно для огромных массивов данных, а второе — для точных расчётов с большими числами. Но на практике ещё не встречается компиляторов, которые смогли бы верно распознать длину типа автоматически. Тенденции как раз обратные — всё приводится к одному типу большой разрядности (например, в Python — 64 бита).
Что-то не верится мне, что в ближайшее время такой умный компилятор появится, очень трудно диапазоны чисел вывести. Ну разве что в самых крайних, а потому бесполезных на практике случаях.
Цитата:
Тот железный факт, что в основу любого типа ложится конкретная разрядность, конечно не способствует появлению Оберон-программ, ориентированных на "универсальное целое", и, полагаю, нам при проецировании типов на конкретные разрядности следует придерживаться традиций платформы и здравого смысла.
Вот и я об этом же говорю.
Цитата:
Кстати, отмечу, что в Си, например, при взгляде на присваивание a = b нельзя заведомо сказать, не зная контекста выполнения и типов переменных, может ли быть здесь потеря разрядности. На Обероне, благодаря его SHORT, — это видно сразу. Притом Вирт, благодаря его концепции "поглощения меньших типов большими, а целых — вещественными", сделал 2 полезных вещи: во-первых, явно видно, где разрядность может быть понижена, а во-вторых не нужно её явно повышать там, где она и так не потеряется. Не нужно явно приводить целые к вещественным (ведь такое приведение осуществляется без потерь). И в то же время, никто не мешает явно повышать разрядность с помощью LONG. Вобщем, придумано явно удачнее, чем в Си.
Согласен, мне тоже нравится такая система целых типов. Моя фраза «надо что-то поконкретнее» означала «учет разной разрядности типов Ofront потребовал усложнения кода, а уж если кто-то скажет, что и в самом ББ INTEGER может быть не 4-байтовый и надо Ofront писать с учетом этого…». Тогда наверно я эти рекомендации просто проигнорирую, и продолжу писать для конкретной реализации ББ. Всё равно очень много придется изменять, если вдруг типы в самом ББ сменятся.
Цитата:
Я понял, что в идеале SHORT не надо использовать для отбрасывания старших битов. Да, ББ и, тем более, Ofront не отловят такое использование SHORT. Но по замыслу SHORT применяется только для того, чтобы сказать: вот обычное целое переходит в короткое целое (или длинное переходит в обычное целое), это волевой акт работы ума программиста, но программист не отбрасывает биты (его на данном этапе даже не интересует как представлено число в памяти машины), он просто явно указывает на понижение мощности типа, что добавляет программе ясности и лёгкости чтения/понимания.
И в идеале конечно если при выполнении SHORT результат усекается, умный рантайм должен обязательно предупредить. Таким образом, программисту становится ценнее каждый бит информации, и его приходится учитывать, а не отбрасывать. Поэтому сейчас я, например, не просто привожу INTEGER к BYTE, а добавляю MOD 256. Код становится немного больше (хотя умный компилер может и такое оптимизировать), но повышается понятность. Снижается зависимость результата вычислений от рамок типа. Так что советую не отсекать старшие биты SHORT'ом, не в этом его соль
Т.е использовать SHORT нужно только для смены типа, а не для смены значения? Или можно сказать так: «нехорошо писать SHORT(L), если при этом может получиться L # SHORT(L)». А если уж нам нужно и правда отбросить биты и изменить значение, то указываем это явно при помощи MOD или BITS или еще каких-то средств именно для манипулирования битами. Что же, разумно, склонен согласиться.
Можно написать,конечно, (для 1 байтового параметра):
Код: "OBERON"
  1. node^.conval^.intval := SHORT(SHORT (v MOD 256))
но в данном случае этот MOD 256 ничего не отбрасывает :) . Потому что количество итераций вычисляется только для случая A<=B, при этом величина B-A в пределах 0..255, так что переноса в другой байт не происходит и MOD 256 ничего не делает.
И в ближайшем будущем это никакой компилятор не обнаружит. Ведь это я знаю, что хотя А,В у нас моделируются переменными типа INTEGER, но в них не попадет значение вне диапазона -128..127. Ofront просто зафиксирует в этом случае ошибку и никакого вычисления количества итераций делать не будет, а значит не будет и вызова SHORT (v MOD 256). Компилятор должен для таких рассуждений обладать мощным искусственным интеллектом, что появится не скоро.
Рассуждения для однобайтового типа и положительного шага справедливы и в общем случае – количество итераций (вернее разность ABS(В-А) )можно уместить в беззнаковой переменной того же размера, что и знаковый параметр цикла. Так что MOD совершенно ничего не делает, лучше написать комментарии, а не MOD.
А что у нас вообще делают NewUnsignedConst и SetUnsignedType? А они «кодируют» беззнаковую константу в виде знаковой (например, 130 кодируется как «-126»). Это же делает и Platform.Unsigned. И тем же способом, кстати, - при помощи SHORT. И тоже без всякого MOD, потому что рекомендуется использовать, только в случае, когда нет выхода за пределы байта (т.е. в этом же случае, как и у меня). Да, я в курсе ,что Platform.Unsigned потом подменяется спецмакросом Си, но в Оберон-модуле он задан именно так.
Но строго говоря, отбрасывание битов происходит и в этом случае. Потому что в знаковой константе старший бит является знаковым. Если мы усекаем многобайтовое целое до байта, то отбрасываем этот знаковый бит. И вместо отброшенного знаковым становится старший бит байта, который до этого был битом «значения» (у него был положительный вес 128). Т.е. за пределы байта не выходим, а один бит всё равно отбрасываем, потому и получается L # SHORT(L).
Цитата:
Таким образом, программисту становится ценнее каждый бит информации, и его приходится учитывать, а не отбрасывать…(Вместе с тем понятно, что при понижении разрядности отрицательных — биты по-любому будут отброшены).
Для отрицательных чисел единичный бит, которым заполнены старшие байты – это то же самое, что и нулевой бит для положительных (точнее неотрицательных чисел, ведь ноль тоже может быть). Т.е. старшие 00000 или 11111 – это «растащенный по лишним байтам» знаковый бит, «заполнитель пустого места». «Лишний», «пустое» означает, что значение данной величины попадает в диапазон меньшего целого типа, мы можем ее записать при помощи меньшего количества байтов. Так что нельзя сказать, что нулевые биты менее ценные ,чем единичные. Не являются ценными одинаковые биты (все нули или все единицы) идущие подряд начиная с самого старшего, их мы можем отбросить не изменяя значение константы (вернее отбросить их все кроме самого правого). Ну и конечно отбрасывать их мы должны группами, чтобы получить размер следующего меньшего целого типа.
----------------
Олег, я не проигнорировал оставшуюся часть твоего сообщения. Отвечу на нее позднее :)

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 31 май 2013, 01:28 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
С вышенаписанным абсолютно согласен, поэтому продолжу рассуждения про формулу вычисления количества повторов цикла FOR. :)

Общий вид:
Код: "C"
_for__x = __DIV(B - A, Step) + 1; /* При положительном шаге Step. */
_for__x = __DIV(A - B, -Step) + 1; /* При отрицательном шаге Step. */
Для простоты возьму цикл с положительным шагом. Что мы хотим сказать разностью B и A? Очевидно, что это вычисляется количество приращений цикла для единичного шага. Вмещается ли результат в переменную _for__x? Безусловно, да, но только если считать её беззнаковой. Проверим наши расчёты. Цикл от 0 до 255 получит 255 приращений; цикл от -128 до 127 получит 127 - (-128) = 7FH - 80H = 255 приращений. Здесь всё верно как при знаковых, так и при беззнаковых значениях.

Деление на шаг должно дать количество приращений цикла уже не для единичного, а для требуемого расчётного шага. Однако мы знаем, что это знаковое деление, и вот тут-то и зарыта проблема. Оно не даст верного результата для нашего псевдобеззнакового делимого. Мы отказались от приведения к бОльшему типу для деления, ибо здесь есть трудности, например, если типы INTEGER и LONGINT имеют одинаковую разрядность, что не противоречит стандарту и возможностям, заложенным в Ofront (хоть задавать одинаковую разрядность и непрактично).

Так что такое повышение разрядности нам ничего не даст. Возникает потребность в беззнаковом делении, которое будет интерпретировать знаковые аргументы как беззнаковые. И вот здесь может помочь сишный unsigned:
Код: "C"
_for__x = __DIV((unsigned)(B - A), Step) + 1; /* При положительном шаге Step. */

Такой __DIV сишный компилятор будет понимать как беззнаковый. И, по идее, такой unsigned не будет повышать разрядность, хотя чего-то сомнительно, на самом деле. Ну и есть сомнения, что это хорошо ляжет на структуру Ofront'а.

Поэтому другое предложение. Определим новую операцию в SYSTEM.h (ну и в Ofront'е конечно тоже) — беззнаковое деление __UDIV, которое будет принимать любые аргументы, но делать вид, что их типы — беззнаковые. Задать реализацию этой операции мы сможем в SYSTEM.h (для Z80 — хоть вызовом асмовой процедуры деления, а хоть прямой генерацией inline-кода простого вычитания в цикле).

Далее всё понятно. У нас есть количество приращений цикла. А нам нужно количество его повторов, что является полученным результатом, увеличенным на 1. Здесь есть та особенность, и мы про неё не забыли, что если цикл будет на весь диапазон значений, то вот тут-то при приращении единицы мы получим переполнение и результат 0, что не должно нас смущать, ибо if (A <= B) мы уже сделали и убедились, что количество выполнений цикла уже точно не 0. Так что такой код будет работать.

Итоговая формула, в рамках любого типа (с учётом того, что этот тип поддерживает __UDIV):

Код: "C"
#define __UDIV(a, b) (((unsigned)a) / ((unsigned)b)) /* Допустим, пока так. */
 
_for__x = __UDIV(B - A, Step) + 1; /* При положительном шаге Step. */
_for__x = __UDIV(A - B, -Step) + 1; /* При отрицательном шаге Step. */
Здесь может показаться, что предвидится проблема с изменением знака числа для шага -128, -32768 и т.д, но из-за того, что шаг цикла — заведомо константа, мы можем скорректировать количество выполнений цикла с таким шагом ещё на этапе компиляции. Кстати, да, а ведь шаг для типа SHORTINT {-128..127} может быть допустим в пределах {-255..255}, больше, видимо, не имеет смысла. Как раз столько, чтобы осталась возможность задавать циклы FOR s := -128 TO 127 BY 255 и FOR s := 127 TO -128 BY -255.


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 31 май 2013, 21:12 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Zorko писал(а):
Хватит ли нам 3 + 1 целочисленных типа или придётся всё равно в итоге добавить-таки HUGEINT? Пока что вроде бы хватает, я как-то скептически отношусь к 128-битной разрядности. Если такие процессоры и станут массовыми, оставшиеся 32 и 64 они целиком не потеснят. Адресовать 64-мя битами уже можно дофига памяти, да и расчёты врятли будут переводиться все поголовно под 128. Так что вот тогда-то нам и понадобится, быть может, HUGEINT. А может быть и нет.
Я тоже думаю, что о большей разрядности чем 64 сейчас думать не следует. Даже 64 не так просто сделать, потому что в ББ это наибольший целый тип и мы не можем обрабатывать переполнение переходом к большей разрядности.

А теперь, что касается переполнения в вычислении количества итераций. Сейчас я как раз занимаюсь классификацией различных видов цикла FOR в зависимости от константности параметров, величины шага, типа параметра и т.д. И в эту систему плохо вписываются наши нововведения – то, что в Ofront еще нет, но что мы хотим добавить: беззнаковые типы, 64-разрядность и расширенный шаг цикла. Расширенный шаг цикла связан с беззнаковыми типами, 64 разрядность требует особого подхода, потому что в ББ нет 128 разрядных целых чисел. Ну и конечно, 64 бита и беззнаковые – это глобальная модификация Ofront, начинать ее следует со структур данных, а не от обработки FOR в StatSeq. Поэтому пока построю FOR без этих нововведений (хотя уже и прикидываю, что понадобится изменить для них).

Повторю еще раз, то что было уже сказано ранее, но немного по-другому.
Итак, мы должны вычислить количество итераций цикла FOR id := A TO B BY Step DO
Общая формула для Step>0, A<=B выглядит как: NumIters = (B - A) DIV Step +1
Формула верная, но В-А может вызвать переполнение ,т.к. принадлежит диапазону 0.. MAX(T) – MIN(T)=2 * MAX(T) +1=2^N -1 (для N-битового знакового типа Т, которому принадлежат А и В). А само количество итераций находится в диапазоне 1.. 2 * MAX(T) + 2 = 2^N. Видим, что и количество итераций и разность (B - A) не входит в диапазон Т: MIN(T)..MAX(T).

Какими путями можно решить эту проблему?
1) В Си, в отличие от Оберона, есть безнаковые числа в диапазоне 0..2^N -1. В исходнике Си вспомогательную переменную объявляем как беззнаковую. Число 2 * MAX(T) + 2= 2^N почти входит в этот диапазон. Не хватает одного значения, зато у беззнаковой величины есть «лишний» ноль. Вот этим нулем и «закодируем» число 2^N. Если мы сначала будем делать декремент, а потом проверять равенство нулю для прекращения цикла, то 0 и сработает как 2^N.

2)Вспомогательную переменную в Си, как и в Обероне объявляем знаковой того же типа, что и переменная цикла id. Но у знаковой переменной диапазон значений - (2 ^ (N -1)) . . 2^(N-1) = {- (2 ^ (N -1)) . . 0 } + {1.. 2^(N-1) }. А нам нужно 1..2^N = {1.. 2^(N-1) } + {2^(N-1)+1 .. 2^N }.
Видим что у этих диапазонов есть общая часть (пересечение) {1.. 2^(N-1) }. А остальные беззнаковые числа «закодируем» по формуле SignesConst= UnsignedConst - 2^N. В частности получится, что 2^N закодировано нулем, как и в предыдущем случае. Если мы построим цикл так, как указано в предыдущем случае, то из-за переполнения получим именно столько итераций, сколько и нужно.

В обоих случаях количество итераций может вычисляться либо самим компилятором Ofront либо динамически, во время выполнения Си-программы. Если А и В являются константами, то количество итераций может вычислить сам Ofront и вставить в исходник Си только нужную константу. Если же значение А или В заранее неизвестно, то вычислением этой константы занимается программа на Си, а Ofront должен сгенерировать необходимые Си-операторы.

Какой из путей выбрать? Для первого случая мы должны заставить Ofront определить в исходнике Си беззнаковую переменную, что штатными средствами невозможно, нужны специальные модификации . Кроме того Ofront должен уметь работать с такими беззнаковыми величинами, чтобы сгенерировать нужную константу. Во втором случае, переменная задается штатным способом, но Ofront должен вычислить «закодированную» константу.

Думаю не так и важно, знаковая или беззнаковая переменная задается в Си. Не так и важно какие константы ей присваивает Ofront, потому что в Си целые – это действительно лишь цепочки битов, которые легко преобразуются друг в друга. Так что вспомогательную переменную можно оставить знаковую (того же типа, что и параметр цикла). Я решил проблемы с переполнением (B - A) при вычислении самим Ofront просто переходом к большей разрядности. При вычислении внутри ББ это самый простой способ. Если мы введем 64-бита, то это не поможет, но я уже придумал специальный способ и для этого случая, скоро его опишу. Этот способ совершает все операции в той же разрядности и не вызывает переполнения. Так что проблем с вычислением количества итераций при компиляции вроде бы нет.
А вот что делать с динамическим вычислением? Переходить к вычислению с большей разрядностью в динамике конечно же нельзя. Придуманный способ вычисления в той же разрядности непрактичен, т.к. требует 2 деления нацело и 2 операции модуль (не считая сравнений и прочего).
Zorko писал(а):
Поэтому другое предложение. Определим новую операцию в SYSTEM.h (ну и в Ofront'е конечно тоже) — беззнаковое деление __UDIV, которое будет принимать любые аргументы, но делать вид, что их типы — беззнаковые. Задать реализацию этой операции мы сможем в SYSTEM.h (для Z80 — хоть вызовом асмовой процедуры деления, а хоть прямой генерацией inline-кода простого вычитания в цикле). ...
#define __UDIV(a, b) (((unsigned)a) / ((unsigned)b)) /* Допустим, пока так. */
А вот это, думаю, подходит лучше всего!Вот что и важно, так это именно деление! В Обероне особое несимметричное деление нацело, которое совпадает с привычным «симметричным» только при делении положительных чисел. Кроме того, Ofront заменяет деление на сдвиг при делителе равном степени двойки. Все это создает проблемы, если (B - A) из-за переполнения станет отрицательным. Поэтому в Си нужно сделать величину (B - A) беззнаковой и использовать специальное беззнаковое деление. Для Ofront это будет специальная неизвестная операция, поэтому он ее не заменит на сдвиг.

Цитата:
Здесь есть та особенность, и мы про неё не забыли, что если цикл будет на весь диапазон значений, то вот тут-то при приращении единицы мы получим переполнение и результат 0, что не должно нас смущать, ибо if (A <= B) мы уже сделали и убедились, что количество выполнений цикла уже точно не 0. Так что такой код будет работать.
И вот тут-то окажется, что я наврал, когда говорил, что в SHORT (v MOD 256) модуль ничего не отбрасывает перед усечением. :) Единичный шаг и перебор всего диапазона - это единственный случай, когда количество не влазит в байт (или сколько там отведено) и 1 битик выходит за пределы. Конечно, результат будет верный и с модулем и без. Стоит ли писать MOD 256 из-за этого единичного случая? И, кстати, в этом случае (ABS(Step)=1) никакого деления не понадобится, а переполнение не страшно. Так что, скорее всего, этот случай будет в особой ветке, когда я все виды FOR классифицирую.

Цитата:
Здесь может показаться, что предвидится проблема с изменением знака числа для шага -128, -32768 и т.д, но из-за того, что шаг цикла — заведомо константа, мы можем скорректировать количество выполнений цикла с таким шагом ещё на этапе компиляции.
Если Си будет толковать такую константу как беззнаковую, то ничего в ней особенного нет (число как число, даже не на краю, а посередине диапазона). Но до этого с ней должен иметь дело Ofront. Из-за того, что у этой константы нет противоположного (она сама себе противоположна) могут возникнуть трудности. Так что, действительно, лучше обработать ее отдельно.


Цитата:
Кстати, да, а ведь шаг для типа SHORTINT {-128..127} может быть допустим в пределах {-255..255}, больше, видимо, не имеет смысла. Как раз столько, чтобы осталась возможность задавать циклы FOR s := -128 TO 127 BY 255 и FOR s := 127 TO -128 BY -255.
Да, когда-то я предлагал ввести именно такой диапазон для шага. Но здесь трудности опять-таки с беззнаковыми величинами. Необходимо сначала ввести в Ofront беззнаковые типы, причем полноценные, а не как сейчас.

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 72 ]  На страницу Пред.  1, 2, 3, 4, 5, 6, 7, 8  След.

Часовой пояс: UTC + 2 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
Создано на основе phpBB® Forum Software © phpBB Group
© VEDAsoft Oberon Club