Programming

How to affect Delphi XEx code generation for AndroidARM targets

19 September 2026 · 15 min read

How to affect Delphi XEx code generation for AndroidARM targets

Delphi XE’s introduction of cross-platform development capabilities, particularly for Android/ARM targets, revolutionized mobile application development for Delphi developers. However, achieving optimal performance and desired behavior on these targets often requires a deeper understanding of how to affect Delphi XE code generation. Mastering the nuances of compiler directives, platform-specific libraries, and targeted optimization techniques allows you to fine-tune your Delphi applications, ensuring they run efficiently and reliably on Android devices. This guide will explore various methods to influence and optimize the code generation process in Delphi XE for Android/ARM, helping you leverage the full potential of the platform and overcome common challenges encountered during development. Whether you’re aiming to squeeze out extra performance, reduce application size, or ensure compatibility with specific hardware, this article provides the insights and techniques you need to succeed. We’ll delve into practical examples and best practices, empowering you to take control of the code generation process and deliver exceptional Android applications.

Understanding Delphi XE Code Generation for Android/ARM

Delphi XE uses a specific code generation pipeline for Android/ARM targets, transforming your Pascal code into native ARM instructions. The Free Pascal Compiler (FPC) is the core of this process, responsible for parsing, optimizing, and ultimately generating the machine code. Understanding this pipeline is crucial for influencing the final output. Factors like compiler version, optimization levels, and the use of specific RTL (Runtime Library) routines significantly impact the generated code. Consider that different Android devices have varying ARM architectures (e.g., ARMv7, ARM64), and Delphi allows you to target specific architectures, which affects code generation.

The Delphi RTL includes platform-specific implementations for many common functions. These implementations are optimized for the target platform, leveraging ARM-specific instructions and features. Using the correct RTL routines is essential for achieving optimal performance. For example, using optimized string manipulation functions or floating-point arithmetic routines can dramatically improve the speed of your application. Furthermore, Delphi’s support for NEON instructions (ARM’s SIMD instruction set) can be leveraged for performance-critical sections of code, such as image processing or audio decoding. According to Embarcadero’s documentation, utilizing platform-specific RTL units can yield up to a 30% performance improvement in certain scenarios. Delphi official website offers further details on platform-specific RTL usage.

Compiler directives play a significant role in controlling code generation. Directives like {$OPTIMIZATION ON} and {$INLINE ON} can enable or disable specific compiler optimizations. Conditional compilation directives (e.g., {$IFDEF ANDROID}) allow you to write platform-specific code that is only compiled for Android targets. Furthermore, the {$ALIGN} directive can be used to control memory alignment, which can impact performance on ARM architectures. Carefully using these directives can significantly influence the generated code and optimize it for the Android/ARM platform. For instance, aligning data structures on 4-byte boundaries can prevent unaligned memory access penalties, which can be significant on some ARM processors.

Compiler Directives and Options for Optimization

Compiler directives are powerful tools for controlling Delphi’s code generation process. The {$OPTIMIZATION} directive, when set to ON, enables a range of optimizations that can improve performance. These optimizations include dead code elimination, constant folding, and instruction scheduling. The {$INLINE} directive encourages the compiler to inline small functions, reducing function call overhead. However, excessive inlining can increase code size, so it’s important to use this directive judiciously. The {$CODEALIGN} directive allows you to control the alignment of code blocks, which can improve instruction cache performance. This is especially relevant for ARM processors, where misaligned code can lead to performance penalties. The featured snippet-optimized paragraph is the following: By setting the {$OPTIMIZATION ON} directive, Delphi can automatically optimize your code for speed and size, often resulting in significant performance gains on Android/ARM devices. This directive enables a suite of techniques that reduce code redundancy and improve instruction scheduling.

Beyond basic optimization, Delphi offers more advanced compiler options that can further fine-tune code generation. The “Optimization level” setting in the project options allows you to choose between different levels of optimization, ranging from “None” to “Maximum.” Higher optimization levels can result in better performance, but they may also increase compilation time and potentially introduce subtle bugs. Experimenting with different optimization levels is recommended to find the best balance for your application. Additionally, the “Code generation” options allow you to control various aspects of code generation, such as the target ARM architecture and the use of floating-point instructions. Carefully configuring these options can significantly impact the performance and compatibility of your application.

Conditional compilation using {$IFDEF} directives is essential for writing platform-specific code. This allows you to use different code paths for Android/ARM targets compared to other platforms. For example, you might use platform-specific APIs or libraries to access hardware features that are not available on other platforms. Conditional compilation also allows you to optimize code for the specific characteristics of the Android/ARM platform. For example, you might use NEON instructions for image processing on ARM devices while using a different approach on other platforms. Using conditional compilation effectively is crucial for creating cross-platform applications that perform well on all target platforms. For example, the following code block shows usage of conditional compilation:

{$IFDEF ANDROID} // Android-specific code here uses Androidapi.JNI.GraphicsContentViewText; {$ELSE} // Other platform code here {$ENDIF} 

Leveraging Platform-Specific Libraries and APIs

Android provides a rich set of platform-specific libraries and APIs that can be accessed from Delphi using the Java Native Interface (JNI). JNI allows you to call Java code from your Delphi application, giving you access to Android’s hardware features, UI elements, and other platform-specific functionality. This is essential for creating applications that take full advantage of the Android platform. Using JNI effectively requires a good understanding of both Delphi and Java, as well as the Android API. Delphi’s Android bridge simplifies some of the JNI complexities, but a solid understanding of the underlying mechanisms is still important. You can find more information on JNI on the Android Developer website.

When using JNI, it’s important to minimize the overhead of crossing the Delphi/Java boundary. Each JNI call incurs a performance penalty, so it’s best to batch multiple operations into a single JNI call whenever possible. For example, instead of calling a Java method repeatedly to set individual pixel values in an image, you could pass an array of pixel values to a single Java method that sets all the pixels at once. Furthermore, consider using direct buffers for transferring data between Delphi and Java. Direct buffers allow you to access memory directly from Java without copying, which can significantly improve performance. According to performance tests, direct buffers can speed up data transfer by 20-50%. Remember to always release JNI objects correctly to avoid memory leaks.

Delphi’s FireMonkey framework provides a cross-platform UI framework that allows you to create user interfaces that look and feel native on multiple platforms, including Android. FireMonkey provides a set of UI components that are rendered using GPU acceleration, resulting in smooth and responsive user interfaces. However, it’s important to optimize your FireMonkey UI for Android to achieve the best possible performance. This includes minimizing the number of UI components, using efficient drawing techniques, and avoiding unnecessary animations. Also, ensure you are using the correct anchoring and alignment properties to provide a responsive UI on multiple Android screen sizes. Consider using native Android UI components for performance-critical parts of your application. Remember that you can mix FireMonkey and native Android UI elements using JNI.

Optimization Techniques for ARM Architectures

ARM processors have specific characteristics that can be exploited to improve performance. One important consideration is memory alignment. ARM processors perform best when data is aligned on 4-byte boundaries. Misaligned data access can result in performance penalties. You can use the {$ALIGN} directive to control memory alignment in Delphi. For example, {$ALIGN 4} ensures that all data structures are aligned on 4-byte boundaries. Similarly, consider using data types that are naturally aligned, such as integers and pointers. According to ARM’s optimization guide, proper data alignment can improve memory access speed by up to 15%. ARM Developer Website has further details on optimization for ARM architectures.

Another important optimization technique is to leverage NEON instructions. NEON is ARM’s SIMD (Single Instruction, Multiple Data) instruction set, which allows you to perform the same operation on multiple data elements simultaneously. NEON instructions are particularly useful for image processing, audio decoding, and other computationally intensive tasks. Delphi provides access to NEON instructions through inline assembly or through specialized libraries. Using NEON instructions can significantly improve the performance of these tasks. However, it’s important to understand the intricacies of NEON programming to avoid common pitfalls, such as data alignment issues and register allocation problems. Consider using existing NEON-optimized libraries instead of writing your own code from scratch, if available. Example of leveraging NEON instructions include the use of Intrinsics.

Code size is an important consideration for mobile applications, as it affects download size and installation time. Reducing code size can also improve performance by reducing memory footprint and improving instruction cache utilization. Delphi provides several techniques for reducing code size, including dead code elimination, code compression, and the use of shared libraries. Dead code elimination removes unused code from the executable, while code compression reduces the size of the executable by compressing the code section. Shared libraries allow you to share common code between multiple applications, reducing the overall code size. It’s important to strike a balance between code size and performance, as aggressive code size optimization can sometimes negatively impact performance.

Troubleshooting Common Issues

Developing for Android/ARM with Delphi XE can present unique challenges. One common issue is application crashes or instability. These crashes can be caused by a variety of factors, including memory leaks, null pointer dereferences, and exceptions in Java code. Debugging these crashes can be difficult, as the stack traces may not always provide clear information about the root cause. Using a debugger and logging extensively can help identify the source of the problem. Consider using tools like Android Studio’s Logcat to monitor the application’s logs and identify potential issues.

Another common issue is poor performance. This can be caused by inefficient code, excessive memory allocation, or slow JNI calls. Profiling your application can help identify performance bottlenecks. Delphi provides built-in profiling tools that can help you identify the parts of your code that are consuming the most time. Additionally, you can use Android’s profiling tools to monitor CPU usage, memory allocation, and network activity. Analyzing these profiles can help you identify areas where you can optimize your code for better performance. Troubleshooting tips provide more details on addressing performance-related concerns.

Compatibility issues can also arise when developing for Android/ARM. Different Android devices have different hardware and software configurations, which can lead to compatibility problems. Testing your application on a variety of devices is essential to ensure that it works correctly on all target devices. Consider using a cloud-based testing service to test your application on a wide range of devices. Additionally, pay attention to Android version compatibility. Older Android versions may not support all of the features used by your application. Use the Android SDK Manager to manage your target SDK versions and ensure compatibility. It’s crucial to thoroughly test your application across a range of Android devices and versions before release.

  • Optimize compiler settings for ARM architecture.
  • Use platform-specific APIs and libraries for better performance.
  1. Identify performance bottlenecks using profiling tools.
  2. Optimize code for memory usage and efficiency.
  3. Test thoroughly on various Android devices.
  • Reduce JNI call overhead by batching operations.
  • Leverage NEON instructions for computationally intensive tasks.

FAQ

Q: How do I target a specific ARM architecture in Delphi XE?
A: You can specify the target ARM architecture in the project options under "Code generation."
Q: What is JNI, and why is it important for Android development in Delphi?
A: JNI (Java Native Interface) allows Delphi code to interact with Java code, providing access to Android's platform-specific APIs and features.
Q: How can I reduce the size of my Delphi Android application?
A: Use dead code elimination, code compression, and shared libraries to minimize the application's footprint.
Optimizing Delphi XE code generation for Android/ARM targets is a multifaceted process that requires a solid understanding of the platform, the compiler, and various optimization techniques. By carefully configuring compiler directives, leveraging platform-specific libraries, and applying ARM-specific optimization strategies, you can significantly improve the performance, stability, and compatibility of your Android applications. Don't hesitate to experiment with different settings and techniques to find the optimal configuration for your specific application. Remember to profile your code regularly to identify performance bottlenecks **Question & Answer :**

Update 2017-05-17. I no longer work for the company where this question originated, and do not have access to Delphi XEx. While I was there, the problem was solved by migrating to mixed FPC+GCC (Pascal+C), with NEON intrinsics for some routines where it made a difference. (FPC+GCC is highly recommended also because it enables using standard tools, particularly Valgrind.) If someone can demonstrate, with credible examples, how they are actually able to produce optimized ARM code from Delphi XEx, I’m happy to accept the answer.


Embarcadero’s Delphi compilers use an LLVM backend to produce native ARM code for Android devices. I have large amounts of Pascal code that I need to compile into Android applications and I would like to know how to make Delphi generate more efficient code. Right now, I’m not even talking about advanced features like automatic SIMD optimizations, just about producing reasonable code. Surely there must be a way to pass parameters to the LLVM side, or somehow affect the result? Usually, any compiler will have many options to affect code compilation and optimization, but Delphi’s ARM targets seem to be just “optimization on/off” and that’s it.

LLVM is supposed to be capable of producing reasonably tight and sensible code, but it seems that Delphi is using its facilities in a weird way. Delphi wants to use the stack very heavily, and it generally only utilizes the processor’s registers r0-r3 as temporary variables. Perhaps the craziest of all, it seems to be loading normal 32 bit integers as four 1-byte load operations. How to make Delphi produce better ARM code, and without the byte-by-byte hassle it is making for Android?

At first I thought the byte-by-byte loading was for swapping byte order from big-endian, but that was not the case, it is really just loading a 32 bit number with 4 single-byte loads.* It might be to load the full 32 bits without doing an unaligned word-sized memory load. (whether it SHOULD avoid that is another thing, which would hint to the whole thing being a compiler bug)*

Let’s look at this simple function:

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

Even with optimizations switched on, Delphi XE7 with update pack 1, as well as XE6, produce the following ARM assembly code for that function:

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} 

Just count the number of instructions and memory accesses Delphi needs for that. And constructing a 32 bit integer from 4 single-byte loads… If I change the function a little bit and use a var parameter instead of a pointer, it is slightly less convoluted:

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} 

I won’t include the disassembly here, but for iOS, Delphi produces identical code for the pointer and var parameter versions, and they are almost but not exactly the same as the Android var parameter version. Edit: to clarify, the byte-by-byte loading is only on Android. And only on Android, the pointer and var parameter versions differ from each other. On iOS both versions generate exactly the same code.

For comparison, here’s what FPC 2.7.1 (SVN trunk version from March 2014) thinks of the function with optimization level -O2. The pointer and var parameter versions are exactly the same.

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 

I also tested an equivalent C function with the C compiler that comes with the Android NDK.

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

And this compiles into essentially the same thing FPC made:

Disassembly of section .text._Z11ReadIntegerPi: 00000000 <_Z11ReadIntegerPi>: 0: 6800 ldr r0, [r0, #0] 2: 4770 bx lr 

We are investigating the issue. In short, it depends on the potential mis-alignment (to 32 boundary) of the Integer referenced by a pointer. Need a little more time to have all of the answers… and a plan to address this.

Marco Cantù, moderator on Delphi Developers

Also reference Why are the Delphi zlib and zip libraries so slow under 64 bit? as Win64 libraries are shipped built without optimizations.


In the QP Report: RSP-9922 Bad ARM code produced by the compiler, $O directive ignored?, Marco added following explanation:

There are multiple issues here:

  • As indicated, optimization settings apply only to entire unit files and not to individual functions. Simply put, turning optimization on and off in the same file will have no effect.
  • Furthermore, simply having “Debug information” enabled turns off optimization. Thus, when one is debugging, explicitly turning on optimizations will have no effect. Consequently, the CPU view in the IDE will not be able to display a disassembled view of optimized code.
  • Third, loading non-aligned 64bit data is not safe and does result in errors, hence the separate 4 one byte operations that are needed in given scenarios.