Arm64EC

From RAD Studio

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.

Windows Arm64EC

Windows 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 Windows 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 Windows 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:
Windows Arm64EC applications and DLLs require a Arm64 OS and cannot run on Windows x64 systems.


Windows 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.

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

Windows 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).

Windows Arm64EC applications can now both produce and consume dynamic packages.

Dynamic Packages

As of RAD Studio 13.2, dynamic packages are fully supported on Windows Arm64EC target. Applications built with Link with runtime packages enabled will depend on BPLs at runtime, and those BPLs must likewise be compiled for the same Windows Arm64EC target.

Note:
Although Windows Arm64EC can load x64 DLLs, its applications cannot load x64 BPLs. The current DCC64 compiler does not follow the standard ABI. DCCARM64EC aims to align with the standard ABI. Therefore, x64 BPLs produced by the current DCC64 compiler are not supported by Windows Arm64EC applications.

Memory Manager

Windows Arm64EC uses the FastMM4-based memory manager as the default memory manager.

rpmalloc is available as an alternative memory manager for Windows Arm64EC. To enable rpmalloc, add System.RPMalloc as the first unit in the project .dpr file .uses clause.

rpmalloc uses, on average, around twice as much memory as FastMM. However, it is extremely stable and fast. In general, it is slightly faster than FastMM in single-threaded applications and between 2x and 30x faster in multi-threaded applications.

Midas.dll

A Windows Arm64EC version of midas.dll is available for Windows Arm64EC applications.

Debugger

The debug engine RAD Studio uses for Windows on Arm is LLDB. LLDB for Windows on Windows 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 Windows Arm64EC

To use the Windows 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:

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

Next up, build the application:

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.

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 Windows 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
MSWINDOWS
CPU64BITS
WIN64
CPUX64
ASSEMBLER
CPUARM
CPUARM64
ARM64EC
EXTERNALLINKER
(obsolete)
LLVM

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 a Windows 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 Windows Arm64EC 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 Windows Arm64EC 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 and link against that library instead. Windows Arm64EC supports (.a) and (.lib) files for static libraries declared in external function declarations.

function zlibVersion: MarshaledAString; cdecl; external 'libzlib.a';

or

function zlibVersion: MarshaledAString; cdecl; external 'libzlib.lib';
Note:
Use the .a or .lib file extension for static libraries.

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 Windows 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 Windows 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.

Windows Arm64EC applications can also load DLLs built for Windows Arm64EC. However, pure Arm64 DLLs are not compatible with Windows 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 Windows Arm64EC. For more information, see the Microsoft documentation page Windows 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.

Microsoft limitation for app behavior at runtime

Currently, for Windows 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.

See Also