Компилятор Oberon-07

High-level languages programming questions
  • 0CodErr wrote:Процедурные типы и переменные процедурных типов тоже попадают в этот список.
    Если это лишнее — убрать не долго
    Да, это конечно, лишнее.
    0CodErr wrote: Комментарии сейчас могут быть дважды вложенными
    Нормально, на практике бОльшая глубина не требуется.

    При беглом осмотре нашел только один недочет: кроме суффикса "H" (INTEGER), шестнадцатиричные константы могут иметь суффикс "X" (CHAR), последние не подсвечиваются, но должны подсвечиваться как "строки".

    В остальном -- отлично!

    Сейчас пишу транслятор промежуточного байт-кода в ассемблер FASM, заодно и опробую HyppoEdit.
    Байт-код не сложный, но обширный -- около 180 инструкций. Пока обработано только 30. Первые результаты несколько неоднозначные.
    Такой код:

    Code: Select all

    PROCEDURE test (n: INTEGER);
    VAR
        a: INTEGER;
    BEGIN
        WHILE n > 0 DO
            a := 7FFFFFFFH;
            WHILE a > 0 DO
                a := a - 1
            END;
            n := n - 1
        END
    END test;
    выполняется в 3 раза быстрее, чем скомпилированный текущим (старым) компилятором, но в 6-7 раз медленнее, чем Delphi7. Но тут понятно, Delphi хорошо оптимизирует: процедура-лист, две переменные -- два регистра. Мои компиляторы честно загружают и сохраняют переменные при каждом обращении. В более сложных случаях разрыв, наверно, будет меньше. Сделать полноценное распределение регистров, конечно, сложно, но можно будет попробовать выявить в программе места, в которых распределить регистры сравнительно просто (вроде этой процедуры), и транслировать такие процедуры по особым правилам.
  • akron1 wrote:выполняется в 3 раза быстрее, чем скомпилированный текущим (старым) компилятором, но в 6-7 раз медленнее, чем Delphi7
    Ну прогресс ведь уже есть — это главное!
    Какое, кстати, внутреннее соглашение вызова(если не указано явно [stdcall] | [winapi]| [cdecl])?
    akron1 wrote:шестнадцатиричные константы могут иметь суффикс "X" (CHAR), последние не подсвечиваются, но должны подсвечиваться как "строки".
    Пробовал такой регуляркой:

    Code: Select all

    <Regexp text="[0-9][0-9A-F]*X" lead="[^\w]"/>
    Вот здесь:

    Code: Select all

          <Style id="char" name="Char" text="1" bold="0" italic="0" underline="0" clr="Strings" bkclr="#FFFFFFFF">
            <Blocks>
              <Block open=""" close="""/>
              <Regexp text="[0-9][0-9A-F]*X" lead="[^\w]"/>
            </Blocks>
          </Style>
    
    Проверял её в https://regex101.com/ и она там рабочая.
    Но в HippoEDIT она не заработала. Вообще у меня не самая новая версия программы, может просто ещё тег Regexp не поддерживался.
    Ни в одной синтаксической схеме, поставляемой с моей версией, не присутствует ни один тег Regexp.
    В то же время в файлах отсюда https://www.hippoedit.com/syntax_files.php?lang=ru такой тег присутствует во многих местах, в частности в asm_arm_spec.xml:

    Code: Select all

          <Style id="number" extend="true">
            <Blocks>
              <!--  0xFFFF -->
              <Regexp text="0x[0-9A-Fa-f]+\>" lead="[^\w]"/>
              <!-- Binary: 0b1010 -->
              <Regexp text="0b[01]+\>" lead="[^\w]"/>
              <!-- Octal: 01234567 -->
              <Regexp text="0[0-7]\>" lead="[^\w]"/>
              <!-- Decimal  are built-in -->
            </Blocks>
          </Style>
    0CodErr wrote:Вставка шаблонов кода Code templates по Ctrl+Enter.
    Как оказалось, клавишу Ctrl нажимать не всегда обязательно, в большинстве случаев достаточно Enter.
    Есть ещё Ctrl+Пробелautocomplete(автоподстановка по первым введённым буквам).

    Забыл сказать, что если выбрать опцию 'Capture output', тогда то, что компилятор выводит обычно в консоль, будет выведено прямо в редакторе в панель Output.
    Потом его ещё вроде можно регулярками распарсить(Output Pattern).
    Spoiler:
    capture_output_hippo_edit.PNG
    capture_output_hippo_edit.PNG (76.13 KiB)
    Viewed 13129 times
  • 0CodErr wrote:Какое, кстати, внутреннее соглашение вызова(если не указано явно [stdcall] | [winapi]| [cdecl])?
    По умолчанию stdcall.
    Есть нюансы: массивы и записи всегда передаются по ссылке, но если перед формальным параметром-записью указано VAR, то на самом деле там будет передан не один параметр, а два: тип записи и адрес:

    Code: Select all

    PROCEDURE (VAR r: REC);
    procedure (typeof(r), adr(r)); stdcall
    Если формальный параметр -- открытый массив (VAR или нет -- не важно), то кроме адреса передаются длины по всем измерениям:

    Code: Select all

    PROCEDURE (a: ARRAY OF ... );
    procedure (adr(a), len(a, 0), len(a, 1)...); stdcall
    В новом компиляторе я изменил порядок передачи адреса и длин:

    Code: Select all

    PROCEDURE (a: ARRAY OF ... );
    procedure (len(a, 0), len(a, 1)..., adr(a)); stdcall
    Это оказалось удобнее для индексации массива и передачи в другую процедуру. Не знаю, почему я сразу так не сделал... Наверно, долго не обдумавал и просто взял первое что пришло.
  • Может лучше уж опционально транслировать в LLVM ?

    В FASM-никакой выгоды, на 1й взгляд.
  • Новый компилятор отличается от старого не только "более лучшим" качеством результирующего кода, а также исходного (в последнее время я пишу значительно лучше). Отличается архитектура: есть четкое разделение на фронт-энд (исходный код -> промежуточное представление) и бэк-энд (промежуточное представление -> целевой код). Разделение настолько четкое, что сейчас это физически две отдельные программы. Предполагается, что таких бэк-эндов будет несколько, в принципе, можно будет сделать бэк-энд LLVM. Но пока я транслирую в FASM. Часто говорят, что трансляция ЯВУ в ассемблер не имеет никаких преимуществ. Но я с этим не согласен: это позволяет легко контролировать правильность трансляции, первое время не заниматься опкодами и вычислением меток, а потом можно без проблем заменить ассемблерный код на машинный. Как это и было в первый раз. Может быть, LLVM выдаст более эффективный машинный код, но ассемблер x86 -- это единственный низкоуровневый язык который я кое-как знаю. Всё остальное мне нужно сначала прочитать, осмыслить и только потом приступать к трансляции. В общем, пока цель -- FASM, потом FASM -> машинный код, а потом посмотрим...
  • akron1 wrote:Байт-код не сложный, но обширный -- около 180 инструкций.
    А можно, если это не секрет, узнать, почему так много?
  • На самом деле, многие инструкции очень похожи, отличаются знаком операции, порядком и типом операндов.
    Например, операции сравнения: их шесть ">", "<", ">=", "<=", "=", "#". Вроде немного...

    Но во-первых, они могуть применяться к разным типам операндов: целым, вещественным и строковым. Для каждого типа нужна своя операция сравнения. Таким образом, ужЕ получается 18 операций.

    Во-вторых, операнды могут быть константами и переменными (выражениями). Возможны три случая:
    1) оба операнда переменные (выражения)
    2) левый операнд переменная, правый -- константа
    3) правый операнд переменная, левый -- константа
    Если оба операнда -- константы, то это вычисляется при компиляции и код не генерируется. Каждый из этих случаев транслируется в машинный код по своим правилам.

    18*3 = 54

    То же самое можно сказать про арифметические операции: "+", "-" и т. д. А еще есть встроенные в язык процедуры: INC, ODD, LSL ... Вот так и получается ~180.

    Конечно, некоторые инструкции полностью совпадают:

    a > 5: больше(выраж, конст)
    5 < a: меньше(конст, выраж)

    Они одинаково транслируются в машинный код, но на этапе трансляции в промежуточный код, удобнее считать их различными. Пока, во всяком случае, а потом, может я их объединю.
  • akron1 wrote:...
    Ага, теперь всё понял. Благодарю за развёрнутый ответ.
  • akron1 wrote:Новый компилятор отличается от старого не только "более лучшим" качеством результирующего кода, а также исходного (в последнее время я пишу значительно лучше). Отличается архитектура: есть четкое разделение на фронт-энд (исходный код -> промежуточное представление) и бэк-энд (промежуточное представление -> целевой код). Разделение настолько четкое, что сейчас это физически две отдельные программы. Предполагается, что таких бэк-эндов будет несколько, в принципе, можно будет сделать бэк-энд LLVM. Но пока я транслирую в FASM. Часто говорят, что трансляция ЯВУ в ассемблер не имеет никаких преимуществ. Но я с этим не согласен: это позволяет легко контролировать правильность трансляции, первое время не заниматься опкодами и вычислением меток, а потом можно без проблем заменить ассемблерный код на машинный. Как это и было в первый раз. Может быть, LLVM выдаст более эффективный машинный код, но ассемблер x86 -- это единственный низкоуровневый язык который я кое-как знаю. Всё остальное мне нужно сначала прочитать, осмыслить и только потом приступать к трансляции. В общем, пока цель -- FASM, потом FASM -> машинный код, а потом посмотрим...
    Возможно, если почитать и осмыслить чужой байт код - не только LLVM, есть же JVM, .net bc, hhvm итп, то найдутся удачные решения и для своего ?
  • Siemargl wrote:Возможно, если почитать и осмыслить чужой байт код - не только LLVM, есть же JVM, .net bc, hhvm итп, то найдутся удачные решения и для своего ?
    Возможно, но на переправе я ничего уже менять не буду. Вот сделаю, тогда и посмотрю, что и как можно улучшить.
  • akron1 wrote:
    0CodErr wrote:Процедурные типы и переменные процедурных типов тоже попадают в этот список.
    Если это лишнее — убрать не долго
    Да, это конечно, лишнее.
    Убрал процедурные типы и переменные процедурного типа из выпадающего списка.
    akron1 wrote:При беглом осмотре нашел только один недочет: кроме суффикса "H" (INTEGER), шестнадцатиричные константы могут иметь суффикс "X" (CHAR), последние не подсвечиваются, но должны подсвечиваться как "строки".
    Добавил для строковых шестнадцатеричных констант

    Code: Select all

    <Regexp text="[0-9][0-9A-F]*X" lead="[^\w]"/> 
    может в новой версии это заработает(у меня не самая новая).
    Сейчас вместо stdcall|winapi|cdecl также допустимо писать какой-нибудь my_call, то есть, так:

    Code: Select all

        PROCEDURE [my_call] MyProcedure(Param1: INTEGER);
    Ob07_.7z (2.23 KiB)
    Downloaded 364 times
  • Осталась необработана только одна инструкция промежуточного кода. Она довольно сложная, и сходу я не придумал, как её эффективно транслировать в ассемблер. Это новая операция, в старой версии языка её нет и в уже написанных программах естесственно тоже, поэтому пока я отложил её реализацию, но на днях, конечно сделаю. Компилятор успешно транслирует свой собственный код, а также после небольших модификаций удалось скомпилировать и Editor.
    Антивирусы не ругаются на GUI-приложения и я выяснил одну из причин, почему есть такая проблема со старым компилятором. Остается еще много мелких недоработок: не доделан рантайм, нет поддержки KolibriOS, не очень информативные сообщения об ошибках компиляции, нет удаления неиспользуемых процедур, ненадежная работа с плавающей точкой, возможно, что-то еще.
    Новый компилятор создает чуть более компактный код, но код при этом более разнообразный и поэтому хуже сжимается. Похоже, что размер сжатого кода будет всё же чуть больше, чем у старого компилятора. Но зато по скорости большое преимущество.

    Провел ряд тестов на скорость между новым, старым компилятором и tcc.
    1. задача размытия изображения (blur)
    2. рекурсивное вычисление чисел Фибоначчи (fib)
    3. задача о ферзях (queens)
    4. сортировка "пузырьком" целочисленного массива (фиксированной длины и нефиксированной (открытый массив)) (bubble, bubbleOA)

    Результат сравнения со старым компилятором (во сколько раз новый быстрее)

    queens 1.85
    fib 2.52
    blur 5.01 (!)
    bubble 2.20
    bubbleOA 9.20 (!)

    Очень слабым местом старого компилятора была индексация массивов (особенно открытых), что хорошо видно по результатам blur и bubbleOA.

    Результат сравнения с tcc

    queens 1.28
    fib 1.02
    blur 1.01

    Как видно, качество кодогенерации в целом не уступает tcc.
    При этом, в задаче blur идет интенсивная работа с массивами, а Оберон традиционно проверяет индексы при выполнении.
    В задаче fib некоторый оверхед вносит синтаксис Оберона: из-за невозможности возврата из середины процедуры, выполняются лишние действия.
    В задаче queens нет возвратов из середины и работа с массивами менее интенсивная, поэтому здесь tcc остался позади.

    Сортировку на tcc не тестировал, потому что язык C позволяет выполнить ручную оптимизацию: заменить индексацию массива на инкремент указателя, что должно быть гораздо эффективнее.

    Превзойти tcc, это, конечно не великое достижение, но в сравнении с тем, что было, весьма неплохо.
  • Ну, прогресс уже есть — это здорово!
    Было бы ещё интересно сравнить результаты с другими реализациями Oberon(или Modula).
    akron1 wrote:Превзойти tcc, это, конечно не великое достижение, но в сравнении с тем, что было, весьма неплохо.
    Конечно, неплохо! Может можно ещё добиться скорости за счёт оптимизированных стандартных модулей? Тогда меньше пришлось бы париться из-за качества кодогенерации.
  • Компилятор XDS хоть и устаревший, но оптимизирующий, с ним сравнивать неинтересно и он еще имеет настройки оптимизации, вроде отключения проверок.
    Можно будет сравнить с Black Box, он похоже один из лучших среди неоптимизирующих.

    Текущее промежуточное представление плохо подходит для оптимизации. Я выбрал такой промежуточный код для упрощения трансляции, так как цель -- создать не хороший компилятор для x86, а простой, но мультитаргетный.

    Впрочем, не все возможности исчерпаны: можно сделать табличный CASE, счетчики FOR хранить в регистрах. Вообще, компилятор использует только регистры eax, ecx и edx для хранения промежуточных значений выражений. Регистры ebx, esi, edi не используются и можно попробовать задействовать их для хранения часто используемых переменных, но для этого надо делать второй проход и серьезно изменить промежуточное представление.

    Еще можно проанализировать промежуточный код, определить какие последовательности инструкций встречаются наиболее часто и транслировать такие последовательности как единое целое. Можно оптимизировать переходы: сейчас компилятор далеко не всегда делает переход по флагу после сравнения -- часто делается установка байта setCC, test и только потом переход. Но это всё в неопределенном будущем...

    Оптимизация библиотек мало что даст, но из рантайма надо будет выжать как можно больше.
  • Who is online

    Users browsing this forum: No registered users and 2 guests