Arm64EC

From RAD Studio
Jump to: navigation, search

Go Up to LLVM-based Delphi Compilers


RAD Studio Florence version 13.1 introduces support for Windows on Arm, a native target platform for Delphi. Using Delphi, users can now create native Arm binaries that run on Windows on Arm devices (or Windows on Arm Virtual machines running on Macs), without using the Intel emulation layer.

The Delphi Windows on Arm toolchain is based on LLVM version 20; it leverages linkers and some RTL services from the LLVM project, relies on Microsoft UCRT for some core RTL operations, and uses LLDB for debugging.

Arm64EC

Arm64EC (Emulation Compatible) is a Microsoft Application Binary Interface (ABI) for Windows on Arm that allows native Arm64 code to interoperate seamlessly with emulated x64 code, enabling incremental migration by sharing x64 calling conventions and registers.

Windows on Arm can run the following types of binaries:

  • Arm64: Native Windows on Arm applications. Only loads Arm64 modules (DLLs, drivers, etc.).
  • X64: Windows Intel x64 applications (using an emulation engine). Only loads Intel x64 modules (DLLs, drivers, etc.).
  • Arm64EC: Native Windows on Arm applications. It can load both Arm and Intel x64 modules (Intel x64 modules use emulation).

Delphi Windows on Arm applications interface directly with the Windows 64-bit Arm API. Delphi generates Arm64EC binaries, an ABI for Windows on Arm promoted by Microsoft for its flexibility in mixing Intel libraries with a main Arm executable.

Supported Platforms

  • The Arm64EC target platform is available only in the 64-bit IDE, not in the 32-bit IDE.
  • The Windows on Arm applications Delphi creates require Windows 11 on Arm, and it does not work on Windows 10 on Arm.
  • It supports Windows Server 2025 or newer.
Attention:
Arm64EC applications and DLLS require a Windows Arm64 OS and cannot run on Windows x64 systems.


Arm64EC Installation

To install Windows on Arm support in RAD Studio, open the [Feature Manager] dialog and select the new target platform (since Windows on Arm support is not part of the core Windows platform).

This can be done during the initial RAD Studio installation or by navigating to the Manage Features dialog box to activate it.

Feature Manager Dialog.png

Once the feature is installed, you can use it in the 64-bit IDE only, just like any other target platform.

Arm64EC Features

The Delphi support for building Windows on Arm applications is very extensive. It is possible to use all Delphi language features and runtime support, the entire VCL and FireMonkey, and supported database and Internet libraries (excluding deprecated variants).

Attention:
One missing feature is the ability to create dynamic packages (runtime packages). Therefore, there is no support for building RAD Server modules or deploying RAD Server natively on Windows Arm.

Debugger

The debug engine RAD Studio uses for Windows on Arm is LLDB. LLDB for Windows on Arm64EC has one known limitation, the lack of support for “mixed mode” debugging – the ability to debug the Arm executable and trace into the Intel code of a library being invoked.

Using Arm64EC

To use the Arm64EC feature, simply create an application and select the platform. For example, create a new VCL application (in the 64-bit IDE), and add the new platform to it, as follows:

Select platform arm64ec.png

This results in two available platforms, Windows 64-bit and Windows on Arm 64-bit:

Target platform arm64ec.png

Next up, build the application:

Arm64ec run.png

It is not possible to run this newly created binary on a Windows PC with an Intel processor. It must be executed on a Windows computer or on a VM powered by an ARM CPU. Running such an application on an ARM computer displays a native app in the Details view of the Task Manager.

Task manager arm64ec.png

Migration Suggestions

This section provides recommendations when migrating your code. Most of your existing Delphi Windows code will recompile and run; however, some areas require special attention and are listed in the sections below.

Conditional Defines

Use the Arm64EC compiler define (but also MSWINDOWS and CPUARM64) to introduce conditional code. The table below shows the different conditional defines for Windows 64-bit vs. Windows 64-bit Arm EC.

Name Windows 64-bit Windows Arm 64-bit EC
DCC
Allowed.png
Allowed.png
MSWINDOWS
Allowed.png
Allowed.png
CPU64BITS
Allowed.png
Allowed.png
WIN64
Allowed.png
Allowed.png
CPUX64
Allowed.png
ASSEMBLER
Allowed.png
CPUARM
Allowed.png
CPUARM64
Allowed.png
Arm64EC
Allowed.png
EXTERNALLINKER
(obsolete)
Allowed.png
LLVM
Allowed.png

For more information, see the Delphi Conditional Compilation.

Windows API

In general, Windows APIs behave as expected. However, some APIs have behaviors that require special attention.

GetNativeSystemInfo

On Windows Arm 64-bit, when called from a fully emulated (x86 or x64) or a partially emulated (Arm64EC) process, the use of this API returns PROCESSOR_ARCHITECTURE_AMD64 in wProcessorArchitecture instead of returning PROCESSOR_ARCHITECTURE_ARM64 to preserve compatibility with emulated binaries that expect a Windows x64 environment.

The same behavior is exposed in Delphi RTL through System.SysUtils.TOSVersion.Architecture, which relies on this underlying Windows API and therefore reports an arIntelX64 architecture in these scenarios.

To reliably detect the native Windows architecture, use IsWow64Process2 instead, as shown below:

begin
  Result := TOSVersion.Architecture;
  // Ensure IsWow64Process2 is available.
  if (TOSVersion.Major >= 10) and (TOSVersion.Build >= 16299) then
  begin
    var ProcessMachine: Word := 0;
    var NativeMachine: Word := 0;
    if IsWow64Process2(GetCurrentProcess, @ProcessMachine, @NativeMachine) then
      case NativeMachine of
        IMAGE_FILE_MACHINE_I386: Result := arIntelX86;
        IMAGE_FILE_MACHINE_AMD64: Result := arIntelX64;
        IMAGE_FILE_MACHINE_ARM64: Result := arARM64;
      end;
  end;
end;

VirtualAlloc

On Windows, Arm64EC applications cannot use VirtualAlloc to allocate executable pages or inject assembly at runtime. The reason is that VirtualAlloc returns pages for x64 instructions, not for Arm64 instructions.

To obtain pages for Arm64 instructions, use VirtualAlloc2 with the flag MEM_EXTENDED_PARAMETER_EC_CODE as shown below:

  var Param: MEM_EXTENDED_PARAMETER;

  FillChar(Param, SizeOf(Param), 0);

Param.TypeAndReserved :=    Ord(MEM_EXTENDED_PARAMETER_TYPE.MemExtendedParameterAttributeFlags);
  Param.ULong64 := MEM_EXTENDED_PARAMETER_EC_CODE;
  
  Block := VirtualAlloc2(GetCurrentProcess, nil, PageSize, MEM_COMMIT,
   
   PAGE_EXECUTE_READWRITE, @Param, 1);

VirtualAllocEx

On a Windows Arm 64-bit system, when using VirtualAllocEx to allocate executable pages and inject assembly at runtime, the resulting memory is determined by the architecture of the target process specified by the hProcess parameter, not by the architecture of the calling process. If the target process is an Arm64EC process, VirtualAllocEx will return executable pages suitable for x64 instructions, not for Arm64 instructions.

To obtain executable pages for Arm64 instructions in this scenario, use VirtualAlloc2 with MEM_EXTENDED_PARAMETER_EC_CODE, as shown above, but informing the desired hProcess instead of GetCurrentProcess.

HeapCreate and HeapAlloc

When using HeapCreate or HeapAlloc in Windows Arm64EC applications to allocate executable memory for runtime code injection, the resulting pages can only execute x64 instructions.

Unlike VirtualAlloc, there is no supported mechanism to allocate executable pages for Arm64 instructions via the heap APIs. If you require Arm64 code injection, replace these APIs with VirtualAlloc2-based allocation strategies.

Inline Assembly Code

Unlike the Delphi compiler for Win32 and Win64, the WinArm64EC compiler does not support inline assembly. Therefore, if your application contains inline assembly (ASM) code, you must examine it and provide a Pascal code equivalent.

A recommendation is to add {$IFDEF ASSEMBLER} to all inline assembly code in your application. See an example below:

function InterlockedBitTestAndReset(Base: PInteger; Offset: Integer): ByteBool;
{$IF not Defined(ASSEMBLER)}
var
  Mask: Cardinal;
  Old: Integer;
begin
  Mask := Cardinal(1) shl (Offset and 31);
  Old := InterlockedAnd(Base^, Integer(not Mask));
  Result := (Cardinal(Old) and Mask) <> 0;
end;
{$ELSEIF Defined(CPUX86)}
asm
      MOV EAX, [EBP+8]
      MOV EDX, [EBP+12]
 LOCK BTR DWORD PTR [EAX], EDX
      SETC AL
end;
{$ELSEIF Defined(CPUX64)}
asm
 LOCK BTR [RCX], EDX
      SETC AL
end;
{$ELSE}
  {$MESSAGE Fatal 'Missing logic for this CPU'}
{$ENDIF}

Linking External Object Files

Like other Delphi compilers based on LLVM, the WinArm64EC compiler does not support merging external object files using the {$L 'foo.o'} or {$LINK 'foo.o'} compiler directives. For more information, see the Link object file (Delphi) page.

When using an LLVM-based DCC, it is possible to eliminate the use of the {$L 'foo.o'} directive by declaring the external symbol directly on the function declaration using the external object syntax:

function zlibVersion: MarshaledAString; cdecl; external object 'zutil.o';

The recommended alternative to linking individual object files is to package them into a static library (.a) and link against that library instead:

function zlibVersion: MarshaledAString; cdecl; external 'libzlib.a';
Note:
Ensure to use the .a file extension for static libraries. Any other extension is interpreted as a DLL attempting to link statically.

To generate a static library, use llvm-ar. See an example below:

llvm-ar.exe rcs libzlib.a zutil.o gzlib.o

Ensure that the resulting .a file is available at link time in the search path or in one of the directories specified to DCC via the -0<paths> argument.

ComponentPlatforms Attribute

When using the ComponentPlatforms attribute on design-time components, explicitly add pidWinArm64EC to enable the component for the Windows 64-bit Arm EC target.

The general recommendation is to use generic constants such as pidAllPlatforms or pfidWindows. These constants automatically cover all compatible Windows targets, see an example below:

type
  [ComponentPlatforms(pfidWindows)]
  TWindowsStore = class(TComponent)
    // …
  end;

DLL Compatibility

With Arm64EC, users can enable a hybrid execution model in which a single process can contain both native Arm64 code and x64 (AMD64) code. This design allows Arm64EC applications to interoperate with existing x64 binaries.

As a result, Delphi applications targeting WinArm64EC can load and use x64 DLLs without restrictions, either through static linking in function declarations, such as procedure Bar; external 'foo.dll'; or through dynamic loading with LoadLibrary and GetProcAddress.

Arm64EC applications can also load DLLs built for Arm64EC. However, pure Arm64 DLLs are not compatible with Arm64EC processes and can only be loaded by pure Arm64 applications, which Delphi currently does not support.

When targeting WinArm64EC, ensure that all dependent DLLs are built for x64 or Arm64EC. For more information, see the Microsoft documentation page Arm64EC for Windows 11 apps on Arm.

Common Errors

Below is a list of common compiler errors that may occur when a project builds successfully for Win32 and Win64, but fails for the WinArm64EC target:

  • Unsupported language feature: 'ASM': Inline assembly is not supported by the WinArm64EC compiler. This error is typically reported when the compiler detects an asm ... end block.
  • Error E1030: Invalid compiler directive: '$L': The WinArm64EC compiler does not support merging external object files via {$L 'foo.o'} (or {$LINK 'foo.o'}).
  • Error E2202: Required package 'designide' not found: The RAD Studio IDE is available only as Win32 (x86) and Win64 (x64), so design-time dependencies (such as designide) are not installed for WinArm64EC.

Known Limitations

The following items are work in progress to address the current known limitations.

Stripping the Relocation table

Stripping a relocation table can cause issues with Windows on Arm because the Windows on Arm emulator (PRISM) needs to control memory mapping, and to do so, it reserves large pieces of memory (typically below 4GB). This means that Windows on Arm PE images often need to be relocated, which requires a relocation table. If that table was stripped using the directive {$SetPEFlags 1}, the Windows on Arm application crashes.

The code below shows a similar situation, using IMAGE_FILE_RELOCS_STRIPPED also causes errors.

uses Winapi.Windows;
{$SetPEFlags IMAGE_FILE_RELOCS_STRIPPED}

This is the same idea as using {$SetPEFlags 1}, since IMAGE_FILE_RELOCS_STRIPPED is a constant with the value of 1.

Dynamic packages unsupported

Support for dynamic packages (BPLs) is not yet available for the WinArm64EC target. As a consequence, runtime loading of packages is not supported.

This limitation does not affect the IDE workflow, which runs on x86 or x64 and continues to support dynamic packages as usual. Third-party libraries can still be compiled, installed in the IDE, and used in WinArm64EC applications without restrictions.

Microsoft limitation for app behavior at runtime

Currently, for Arm64EC, the TOSVersion.Architecture returns the same value as an Intel application (and the same for an Intel app emulated on Windows Arm). This reflects Microsoft's decision to return “false” information to maintain code compatibility.

Class constructors and destructors linkage behavior

On WinArm64EC, all class constructors from units used by the application are linked and executed during the application initialization/finalization, regardless of whether the classes are explicitly referenced.

See Also