Bagaimana cara memengaruhi pembuatan kode Delphi XEx untuk target Android / ARM?

266

Pembaruan 2017-05-17. Saya tidak lagi bekerja untuk perusahaan tempat pertanyaan ini berasal, dan tidak memiliki akses ke Delphi XEx. Sementara saya ada di sana, masalah diselesaikan dengan bermigrasi ke FPC + GCC campuran (Pascal + C), dengan NEON intrinsik untuk beberapa rutinitas di mana ia membuat perbedaan. (FPC + GCC sangat dianjurkan juga karena memungkinkan menggunakan alat standar, khususnya Valgrind.) Jika seseorang dapat menunjukkan, dengan contoh yang kredibel, bagaimana mereka benar-benar dapat menghasilkan kode ARM yang dioptimalkan dari Delphi XEx, saya senang menerima jawabannya .


Kompiler Delphi Embarcadero menggunakan backend LLVM untuk menghasilkan kode ARM asli untuk perangkat Android. Saya memiliki sejumlah besar kode Pascal yang perlu saya kompilasi ke dalam aplikasi Android dan saya ingin tahu bagaimana membuat Delphi menghasilkan kode yang lebih efisien. Saat ini, saya bahkan tidak berbicara tentang fitur-fitur canggih seperti optimasi SIMD otomatis, hanya tentang menghasilkan kode yang masuk akal. Tentunya harus ada cara untuk meneruskan parameter ke sisi LLVM, atau entah bagaimana mempengaruhi hasilnya? Biasanya, setiap kompiler akan memiliki banyak pilihan untuk mempengaruhi kompilasi dan optimisasi kode, tetapi target ARM Delphi tampaknya hanya "optimasi on / off" dan hanya itu.

LLVM seharusnya mampu menghasilkan kode yang cukup ketat dan masuk akal, tetapi tampaknya Delphi menggunakan fasilitasnya dengan cara yang aneh. Delphi ingin menggunakan tumpukan sangat banyak, dan umumnya hanya menggunakan register prosesor r0-r3 sebagai variabel sementara. Mungkin yang paling gila dari semuanya, tampaknya memuat bilangan bulat 32 bit normal sebagai empat operasi beban 1-byte. Bagaimana membuat Delphi menghasilkan kode ARM yang lebih baik, dan tanpa kerumitan byte demi byte yang dibuatnya untuk Android?

Pada awalnya saya pikir byte-by-byte loading adalah untuk bertukar urutan byte dari big-endian, tapi itu tidak terjadi, itu benar-benar hanya memuat angka 32 bit dengan 4 beban byte tunggal. * Mungkin untuk memuat 32 bit penuh tanpa melakukan beban memori berukuran kata yang tidak selaras. (apakah itu HARUS menghindari itu adalah hal lain, yang akan mengisyaratkan semuanya menjadi bug kompiler) *

Mari kita lihat fungsi sederhana ini:

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

Bahkan dengan optimisasi diaktifkan, Delphi XE7 dengan paket pembaruan 1, serta XE6, menghasilkan kode perakitan ARM berikut untuk fungsi itu:

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}

Hitung saja jumlah instruksi dan akses memori yang dibutuhkan Delphi untuk itu. Dan membangun integer 32 bit dari 4 beban byte tunggal ... Jika saya mengubah sedikit fungsi dan menggunakan parameter var alih-alih sebuah pointer, ini sedikit lebih berbelit-belit:

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}

Saya tidak akan menyertakan pembongkaran di sini, tetapi untuk iOS, Delphi menghasilkan kode yang identik untuk versi parameter pointer dan var, dan mereka hampir tetapi tidak persis sama dengan versi parameter Android var. Edit: untuk memperjelas, pemuatan byte-by-byte hanya di Android. Dan hanya di Android, versi parameter penunjuk dan var berbeda satu sama lain. Di iOS kedua versi menghasilkan kode yang persis sama.

Sebagai perbandingan, inilah yang dipikirkan FPC 2.7.1 (versi trunk SVN dari Maret 2014) tentang fungsi dengan level optimisasi -O2. Versi parameter pointer dan var persis sama.

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

Saya juga menguji fungsi C yang setara dengan kompiler C yang datang dengan Android NDK.

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

Dan ini mengkompilasi pada dasarnya hal yang sama yang dibuat FPC:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
Sisi S. Segar
sumber
14
Btw dalam diskusi Google+ tentang ini, Sam Shaw mencatat bahwa C ++ menunjukkan kode bentuk panjang di build debug dan kode yang dioptimalkan dalam rilis. Wheres Delphi melakukannya di keduanya. Jadi dari sana itu bisa jadi bug sederhana di flag yang mereka kirimkan LLVM, dan jika demikian laporan bug sangat layak untuk diarsipkan, mungkin akan segera diperbaiki.
David
9
Oh, ok, saya salah baca. Kemudian, seperti yang dikatakan Notlikethat, kedengarannya seperti itu mengasumsikan beban pointer akan tidak selaras (atau tidak dapat menjamin keselarasan), dan platform ARM yang lebih lama tidak dapat selalu melakukan beban yang tidak selaras. Pastikan Anda memilikinya yang menargetkan penargetan armeabi-v7aalih-alih armeabi(tidak yakin apakah ada opsi seperti itu di kompiler ini), karena beban yang tidak selaras harus didukung sejak ARMv6 (sementara armeabimengasumsikan ARMv5). (Pembongkaran yang ditunjukkan tidak terlihat seperti membaca nilai bigendian, hanya membaca sedikit nilai endian satu byte pada suatu waktu.)
mstorsjo
6
Saya menemukan RSP-9922 yang tampaknya merupakan bug yang sama.
David
6
Seseorang bertanya tentang pengoptimalan yang rusak antara XE4 dan XE5, di newsgroup embarcadero.public.delphi.platformspecific.ios, "Optimasi ARM Compiler rusak?" devsuperpage.com/search/…
Side S. Fresh
6
@ Johan: apa yang bisa dieksekusi itu? Saya mendapat kesan bahwa itu entah bagaimana dipanggang di dalam executable compiler Delphi. Cobalah dan beri tahu kami hasilnya.
Side S. Fresh

Jawaban:

8

Kami sedang menyelidiki masalah ini. Singkatnya, ini tergantung pada potensi mis-alignment (batas 32) dari Integer yang dirujuk oleh pointer. Perlu sedikit waktu lagi untuk mendapatkan semua jawaban ... dan rencana untuk mengatasinya.

Marco Cantù, moderator di Delphi Developers

Juga referensi Mengapa Delphi zlib dan zip libraries sangat lambat di bawah 64 bit? sebagai pustaka Win64 dikirimkan dibangun tanpa optimisasi.


Dalam Laporan QP: RSP-9922 Kode ARM buruk yang dihasilkan oleh kompiler, arahan $ O diabaikan? , Marco menambahkan penjelasan berikut:

Ada beberapa masalah di sini:

  • Seperti yang ditunjukkan, pengaturan optimasi hanya berlaku untuk seluruh file unit dan tidak untuk fungsi individual. Sederhananya, menghidupkan dan mematikan optimasi dalam file yang sama tidak akan berpengaruh.
  • Lebih jauh lagi, dengan mengaktifkan "Informasi debug" mematikan optimisasi. Jadi, ketika seseorang melakukan debugging, menyalakan optimisasi secara eksplisit tidak akan berpengaruh. Akibatnya, tampilan CPU dalam IDE tidak akan dapat menampilkan tampilan kode optimal yang dibongkar.
  • Ketiga, memuat data 64-bit yang tidak selaras tidak aman dan menghasilkan kesalahan, oleh karena itu operasi terpisah 4 byte yang diperlukan dalam skenario yang diberikan.
Kirk Strobeck
sumber
Marco Cantù memposting catatan itu "Kami sedang menyelidiki masalah" pada Januari 2015, dan laporan bug terkait RSP-9922 ditandai diselesaikan dengan resolusi "Works As Expected" pada Januari 2016, dan ada disebutkan "masalah internal ditutup pada 2 Maret, 2015 ". Saya tidak mengerti penjelasan mereka.
Side S. Fresh
1
Saya menambahkan komentar dalam resolusi masalah.
Marco Cantù