Как повлиять на генерацию кода Delphi XEx для целей Android / ARM?

266

Обновление 2017-05-17. Я больше не работаю в компании, где возник этот вопрос, и не имею доступа к Delphi XEx. Пока я был там, проблема была решена путем перехода на смешанный FPC + GCC (Pascal + C) с встроенными NEON для некоторых подпрограмм, где это имело значение. (FPC + GCC настоятельно рекомендуется также потому, что он позволяет использовать стандартные инструменты, в частности Valgrind.) Если кто-то может продемонстрировать на достоверных примерах, как он на самом деле способен производить оптимизированный код ARM из Delphi XEx, я рад принять ответ ,


Компиляторы Embarcadero Delphi используют бэкэнд LLVM для создания собственного кода ARM для устройств Android. У меня есть большое количество кода на Pascal, которое мне нужно скомпилировать в приложения для Android, и я хотел бы знать, как заставить Delphi генерировать более эффективный код. Прямо сейчас я даже не говорю о расширенных функциях, таких как автоматическая оптимизация SIMD, просто о создании разумного кода. Наверняка должен быть способ передачи параметров на сторону LLVM или как-то повлиять на результат? Обычно у любого компилятора будет много опций, влияющих на компиляцию и оптимизацию кода, но цели Delphi ARM кажутся просто «оптимизацией вкл / выкл» и все.

Предполагается, что LLVM способен производить достаточно жесткий и разумный код, но кажется, что Delphi использует свои возможности странным образом. Delphi хочет использовать стек очень интенсивно и обычно использует только регистры процессора r0-r3 в качестве временных переменных. Пожалуй, самым сумасшедшим из всех, кажется, является загрузка обычных 32-битных целых чисел в виде четырех 1-байтовых операций загрузки. Как заставить Delphi создавать лучший ARM-код, и без побайтных хлопот, которые он создает для Android?

Сначала я думал, что побайтная загрузка предназначена для замены порядка байтов в старшей последовательности, но это не так, это просто загрузка 32-битного числа с 4 однобайтовыми загрузками. * Это может быть загрузка полные 32 бита без выравнивания по размеру памяти. (ДОЛЖЕН ли он избегать этого, это другая вещь, которая намекала бы на то, что все это - ошибка компилятора) *

Давайте посмотрим на эту простую функцию:

function ReadInteger(APInteger : PInteger) : Integer;
begin
  Result := APInteger^;
end;

Даже при включенной оптимизации Delphi XE7 с пакетом обновлений 1, а также XE6 создают следующий код сборки ARM для этой функции:

Disassembly of section .text._ZN16Uarmcodetestform11ReadIntegerEPi:

00000000 <_ZN16Uarmcodetestform11ReadIntegerEPi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   78c1        ldrb    r1, [r0, #3]
   a:   7882        ldrb    r2, [r0, #2]
   c:   ea42 2101   orr.w   r1, r2, r1, lsl #8
  10:   7842        ldrb    r2, [r0, #1]
  12:   7803        ldrb    r3, [r0, #0]
  14:   ea43 2202   orr.w   r2, r3, r2, lsl #8
  18:   ea42 4101   orr.w   r1, r2, r1, lsl #16
  1c:   9101        str r1, [sp, #4]
  1e:   9000        str r0, [sp, #0]
  20:   4608        mov r0, r1
  22:   b003        add sp, #12
  24:   bd80        pop {r7, pc}

Просто посчитайте количество инструкций и обращений к памяти, необходимых для этого Delphi. И построение 32-битного целого числа из 4 однобайтовых загрузок ... Если я немного изменю функцию и использую параметр var вместо указателя, это будет немного менее запутанным:

Disassembly of section .text._ZN16Uarmcodetestform14ReadIntegerVarERi:

00000000 <_ZN16Uarmcodetestform14ReadIntegerVarERi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   6801        ldr r1, [r0, #0]
   a:   9101        str r1, [sp, #4]
   c:   9000        str r0, [sp, #0]
   e:   4608        mov r0, r1
  10:   b003        add sp, #12
  12:   bd80        pop {r7, pc}

Я не буду включать здесь разборку, но для iOS Delphi создает идентичный код для версий указателя и параметров var, и они почти, но не совсем совпадают с версией параметра var Android. Изменить: чтобы уточнить, побайтовая загрузка только на Android. И только на Android версии указателя и параметра var отличаются друг от друга. На iOS обе версии генерируют абсолютно одинаковый код.

Для сравнения, вот что FPC 2.7.1 (версия магистрали SVN от марта 2014 г.) думает о функции с уровнем оптимизации -O2. Версии указателя и параметра var одинаковы.

Disassembly of section .text.n_p$armcodetest_$$_readinteger$pinteger$$longint:

00000000 <P$ARMCODETEST_$$_READINTEGER$PINTEGER$$LONGINT>:

   0:   6800        ldr r0, [r0, #0]
   2:   46f7        mov pc, lr

Я также протестировал эквивалентную функцию C с помощью компилятора C, который поставляется с Android NDK.

int ReadInteger(int *APInteger)
{
    return *APInteger;
}

И это компилируется по существу в то же самое, что сделал FPC:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
Сиде С. Фреш
источник
14
Кстати, в обсуждении Google+ об этом Сэм Шоу отмечает, что C ++ показывает длинный код в отладочных сборках и оптимизированный код в выпуске. Wheres Delphi делает это в обоих. Таким образом, это может быть простой ошибкой в ​​флагах, которые они отправляют LLVM, и если так, то отчет об ошибке стоит того, чтобы его зафиксировать, он может быть исправлен довольно скоро.
Дэвид
9
О, хорошо, я неправильно прочитал. Затем, как сказал Нотликетхат, звучит так, будто предполагается, что загрузка указателя будет не выровнена (или не может гарантировать выравнивание), а более старые платформы ARM не обязательно могут выполнять не выровненные загрузки. Удостоверьтесь, что он использует таргетинг при сборке armeabi-v7aвместо armeabi(не уверен, есть ли такие опции в этом компиляторе), так как должны поддерживаться невыровненные загрузки, начиная с ARMv6 (хотя armeabiпредполагается, что ARMv5). (Показанная дизассемблирование не похоже на то, что она читает бигендовское значение, она просто читает небольшое порядковое значение по одному байту за раз.)
mstorsjo
6
Я нашел RSP-9922, который, кажется, является той же самой ошибкой.
Дэвид
6
Кто-то спрашивал о том, что между XE4 и XE5 происходит сбой оптимизации, в новостной группе embarcadero.public.delphi.platformspecific.ios: «Оптимизация компилятора ARM сломана?» devsuperpage.com/search/...
Side S. Fresh
6
@Johan: что это за исполняемый файл? У меня сложилось впечатление, что он каким-то образом запекся в исполняемом файле компилятора Delphi. Попробуйте и дайте нам знать результаты.
Сиде С. Фреш

Ответы:

8

Мы расследуем проблему. Короче говоря, это зависит от возможного неправильного выравнивания (до 32 границ) целого числа, на которое ссылается указатель. Нужно немного больше времени, чтобы получить ответы на все вопросы ... и план для решения этой проблемы.

Марко Канту, модератор Delphi Developers

Кроме того, почему библиотеки Delphi zlib и zip так медленно работают под 64-битной версией? поскольку библиотеки Win64 поставляются встроенными без оптимизации.


В отчете QP: RSP-9922 Плохой код ARM, созданный компилятором, директива $ O игнорируется? Марко добавил следующее объяснение:

Здесь есть несколько проблем:

  • Как указано, параметры оптимизации применяются только ко всем файлам модуля, а не к отдельным функциям. Проще говоря, включение и выключение оптимизации в одном и том же файле не будет иметь никакого эффекта.
  • Кроме того, простое включение «Отладочная информация» отключает оптимизацию. Таким образом, когда выполняется отладка, явное включение оптимизаций не будет иметь никакого эффекта. Следовательно, представление ЦП в IDE не сможет отображать разобранное представление оптимизированного кода.
  • В-третьих, загрузка невыровненных 64-битных данных небезопасна и приводит к ошибкам, следовательно, необходимы отдельные 4-байтовые операции, которые необходимы в данных сценариях.
Кирк Стробек
источник
Марко Канту опубликовал эту заметку «Мы расследуем проблему» в январе 2015 года, а соответствующий отчет об ошибке RSP-9922 был помечен как исправленный с разрешением «Работает как ожидается» в январе 2016 года, и есть упоминание «внутренняя проблема закрыта 2 марта», 2015" . Я не понимаю их объяснения.
Сиде С. Фреш
1
Я добавил комментарий в разрешение проблемы.
Марко Канту,