Cập nhật 2017-05-17. Tôi không còn làm việc cho công ty nơi câu hỏi này bắt nguồn và không có quyền truy cập vào Delphi XEx. Trong khi tôi ở đó, vấn đề đã được giải quyết bằng cách di chuyển sang FPC + GCC hỗn hợp (Pascal + C), với nội tại NEON cho một số thói quen tạo ra sự khác biệt. (FPC + GCC cũng rất được khuyến khích vì nó cho phép sử dụng các công cụ tiêu chuẩn, đặc biệt là Valgrind.) Nếu ai đó có thể chứng minh, với các ví dụ đáng tin cậy, làm thế nào họ thực sự có thể tạo mã ARM được tối ưu hóa từ Delphi XEx, tôi rất vui lòng chấp nhận câu trả lời .
Trình biên dịch Delphi của Embarcadero sử dụng phụ trợ LLVM để tạo mã ARM gốc cho các thiết bị Android. Tôi có một lượng lớn mã Pascal mà tôi cần biên dịch vào các ứng dụng Android và tôi muốn biết cách làm cho Delphi tạo mã hiệu quả hơn. Ngay bây giờ, tôi thậm chí không nói về các tính năng nâng cao như tối ưu hóa SIMD tự động, chỉ là về việc sản xuất mã hợp lý. Chắc chắn phải có một cách để truyền tham số cho phía LLVM, hoặc bằng cách nào đó ảnh hưởng đến kết quả? Thông thường, bất kỳ trình biên dịch nào cũng sẽ có nhiều tùy chọn ảnh hưởng đến việc biên dịch và tối ưu hóa mã, nhưng các mục tiêu ARM của Delphi dường như chỉ là "tối ưu hóa bật / tắt" và đó là điều đó.
LLVM được cho là có khả năng tạo ra mã hợp lý chặt chẽ và hợp lý, nhưng có vẻ như Delphi đang sử dụng các cơ sở của nó một cách kỳ lạ. Delphi muốn sử dụng stack rất nhiều và nó thường chỉ sử dụng các thanh ghi r0-r3 của bộ xử lý làm các biến tạm thời. Có lẽ điên rồ nhất trong tất cả, nó dường như đang tải các số nguyên 32 bit bình thường như bốn hoạt động tải 1 byte. Làm cách nào để Delphi tạo mã ARM tốt hơn và không gặp rắc rối theo từng byte mà nó đang tạo cho Android?
Lúc đầu, tôi nghĩ rằng việc tải từng byte là để hoán đổi thứ tự byte từ big-endian, nhưng thực tế không phải vậy, nó thực sự chỉ là tải một số 32 bit với 4 lần tải byte đơn. * Có thể là để tải 32 bit đầy đủ mà không thực hiện tải bộ nhớ có kích thước từ không được phân bổ. (cho dù nó NÊN tránh đó là một điều khác, điều này sẽ gợi ý cho toàn bộ sự việc là lỗi trình biên dịch) *
Hãy xem chức năng đơn giản này:
function ReadInteger(APInteger : PInteger) : Integer;
begin
Result := APInteger^;
end;
Ngay cả khi tối ưu hóa được bật, Delphi XE7 với gói cập nhật 1, cũng như XE6, tạo ra mã lắp ráp ARM sau cho chức năng đó:
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}
Chỉ cần đếm số lượng hướng dẫn và bộ nhớ truy cập Delphi cần cho điều đó. Và xây dựng một số nguyên 32 bit từ 4 lần tải byte đơn ... Nếu tôi thay đổi hàm một chút và sử dụng tham số var thay vì con trỏ, nó sẽ hơi phức tạp hơn một chút:
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}
Tôi sẽ không bao gồm việc tháo gỡ ở đây, nhưng đối với iOS, Delphi tạo mã giống hệt nhau cho các phiên bản tham số con trỏ và var và chúng gần như không hoàn toàn giống với phiên bản tham số var của Android. Chỉnh sửa: để làm rõ, việc tải từng byte chỉ có trên Android. Và chỉ trên Android, các phiên bản tham số con trỏ và var khác nhau. Trên iOS cả hai phiên bản đều tạo chính xác cùng một mã.
Để so sánh, đây là những gì FPC 2.7.1 (phiên bản trung kế SVN từ tháng 3 năm 2014) nghĩ về chức năng với mức tối ưu hóa -O2. Các phiên bản tham số con trỏ và var hoàn toàn giống nhau.
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
Tôi cũng đã thử nghiệm một chức năng C tương đương với trình biên dịch C đi kèm với NDK của Android.
int ReadInteger(int *APInteger)
{
return *APInteger;
}
Và điều này tổng hợp thành cơ bản giống như điều FPC đã thực hiện:
Disassembly of section .text._Z11ReadIntegerPi:
00000000 <_Z11ReadIntegerPi>:
0: 6800 ldr r0, [r0, #0]
2: 4770 bx lr
armeabi-v7a
thay vì armeabi
(không chắc chắn nếu có các tùy chọn như vậy trong trình biên dịch này), vì tải không được phân bổ sẽ được hỗ trợ kể từ ARMv6 (trong khi armeabi
giả sử ARMv5). (Việc phân tách được hiển thị không giống như nó đọc một giá trị lớn, nó chỉ đọc một giá trị endian nhỏ một byte mỗi lần.)