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

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


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

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

Сначала я думал, что байтовая байтовая загрузка предназначена для замены байтового байта от big-endian, но это не так, на самом деле это просто загрузка 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, и они почти точно не совпадают с версией параметров Android var. Изменить: чтобы уточнить, побайтовая загрузка загружается только на 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 

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

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

    Также ссылка Почему библиотеки Delphi zlib и zip настолько медленны до 64 бит? Поскольку библиотеки Win64 отправляются без оптимизации.


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

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

    • Как указано, настройки оптимизации применяются только к целым файлам устройства, а не к отдельным функциям. Проще говоря, включение и выключение оптимизации в том же файле не будет иметь никакого эффекта.
    • Кроме того, просто включение «Отладочной информации» отключает оптимизацию. Таким образом, при отладке явное включение оптимизаций не будет иметь никакого эффекта. Следовательно, представление ЦП в среде IDE не сможет отобразить разобранное представление оптимизированного кода.
    • В-третьих, загрузка не выровненных 64-битных данных небезопасна и приводит к ошибкам, а следовательно, к отдельным 4-байтным операциям, которые необходимы в данных сценариях.