Skip to content

Native ARM64 Implementation Using Assembler#295

Open
superflexible wants to merge 1 commit intoremobjects:masterfrom
superflexible:master
Open

Native ARM64 Implementation Using Assembler#295
superflexible wants to merge 1 commit intoremobjects:masterfrom
superflexible:master

Conversation

@superflexible
Copy link

Native ARM64 Implementation Using Assembler
Tested on Linux / ARM64 and macOS / M1

Native ARM64 Implementation Using Assembler
Tested on Linux / ARM64 and macOS / M1
@martijnlaan martijnlaan mentioned this pull request Mar 25, 2026
@@ -0,0 +1,375 @@
{$IFDEF CPUARM64}
{$IFDEF MSWINDOWS}
{$L arm64win.obj}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file does not actually exist

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello,
yes, it's for Windows but it will never be used. This should be removed. Thanks fo your comments! I will try to amend the pull request accordingly.

@@ -0,0 +1,375 @@
{$IFDEF CPUARM64}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're supposed to add a new CPU type next to CPU32/CPU64 to eDefines.inc instead of directly checking CPUARM64 and CPUAARCH64? Not entirely sure though.

{$L arm64win.obj}
{$ELSE}
{$IFDEF MACOS}
{$L arm64sysv-macos.o}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be instructions on how to recreate to the .o files, because accepting binaries from a PR seems rather unsafe.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will add comments. The assembler commands are simple. I will also check if inline asm is possible instead. I know that Delphi doesn't allow it for ARM64 but FPC might.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's correct that Delphi doesn't allow inline assembler from ARM64, but it also doesn't allow $L for ARM64.

https://docwiki.embarcadero.com/RADStudio/Florence/en/ARM64EC has this example of the syntax they do allow from ARM64 (but not for x86/x64):

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

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's correct. My pull request is only for FPC, which allows it.

.type arm64call, %function
arm64call:
#endif
stp x29, x30, [sp, #-64]!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps there could be some comments describing the various steps?

Also, does it handle exceptions?

s := '';
end;

function AlwaysAsVariable(aType: TPSTypeRec): Boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidental move?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This had to be moved before the {$ifdef empty_methods_handler} section, otherwise it won't compile if empty_methods_handler is defined because two functions are missing. The upstream version only compiles for Intel CPUs.

{$define empty_methods_handler}
{$ifend}
{$else}
{$if defined(cpuarm)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is cpuarm correct here? That's not ARM64, right. If it's correct and indeed not really ARM64 related then this should be a separate PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On ARM64, CPUARM is also defined. So this covers both ARM32 and ARM64.

{$INCLUDE eDefines.inc}

{$IFDEF FPC}{$H+}{$ENDIF}
{$IFDEF FPC}{$MODE DELPHI}{$H+}{$ENDIF}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change and the one to PascalScriptPFC.inc would stop me from merging the PR since I'm not familiar with FCP. Also the changes don't really seem to be related to ARM64? So they should be a separate PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do this to get it to compile at all, but maybe I can separate it out.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an ARM64 (AArch64) native assembler-based call stub and wires it into the Pascal Script runtime so external/native calls can be invoked on Linux ARM64 and macOS ARM64.

Changes:

  • Add ARM64 invocation implementation (arm64.inc) and route ARM64 builds to include it from uPSRuntime.pas.
  • Add ARM64 SysV assembly entrypoint (arm64sysv.S) plus prebuilt Linux/macOS object files for linking.
  • Adjust FPC include/mode handling in PascalScript.inc / PascalScriptFPC.inc to avoid mode/InvokeCall compilation issues.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Source/uPSRuntime.pas Adds ARM64 include paths and adjusts empty method handler conditional compilation.
Source/arm64.inc New ARM64 InnerfuseCall implementation and external arm64call declaration/linking.
Source/arm64sysv.S New SysV ARM64 assembly implementation of arm64call entrypoint.
Source/arm64sysv-linux.o Prebuilt Linux ARM64 object for the assembly entrypoint.
Source/arm64sysv-macos.o Prebuilt macOS ARM64 object for the assembly entrypoint.
Source/PascalScript.inc Sets FPC mode to Delphi earlier to avoid include-time mode switching issues.
Source/PascalScriptFPC.inc Disables mode switch and USEINVOKECALL define in this include to avoid FPC compilation errors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

var _D0: Double;
constref Registers: TARM64Registers;
Stack: Pointer;
Items: NativeInt); external {$IFDEF DARWIN}name '_arm64call'{$ENDIF};
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Apple targets the assembly symbol is exported as _arm64call, but the external declaration only uses name '_arm64call' under {$IFDEF DARWIN}. Delphi defines MACOS (not DARWIN), so Delphi/macOS will likely fail to link. Use a condition that matches Delphi/macOS as well (e.g., DEFINED(DARWIN) or DEFINED(MACOS)).

Suggested change
Items: NativeInt); external {$IFDEF DARWIN}name '_arm64call'{$ENDIF};
Items: NativeInt); external {$IF DEFINED(DARWIN) OR DEFINED(MACOS)}name '_arm64call'{$IFEND};

Copilot uses AI. Check for mistakes.

procedure arm64call(
Address: Pointer;
out _X0: IPointer;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arm64call reads the initial value of _X0 to populate x0 (first argument), but the parameter is declared as out. Using an out parameter as input is unsafe/misleading and can allow uninitialized reads if the caller changes. Declare _X0 as var (in/out) to reflect that it is both an input (arg x0) and an output (return x0).

Suggested change
out _X0: IPointer;
var _X0: IPointer;

Copilot uses AI. Check for mistakes.
StoreIntReg(IPointer(Cardinal(FVar^.dta^)));
btPChar:
if Pointer(fvar^.dta^) = nil then
StoreIntReg(IPointer(@EmptyPChar))
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EmptyPChar is declared as a PChar, but the code passes @EmptyPChar when a btPChar argument is nil. That passes the address of the pointer variable (PPChar) rather than a pointer to a null-terminated char buffer, which will produce an invalid PChar for callees. Match the existing x86/x64 approach (e.g., define EmptyPchar as an array[0..0] of Char/#0) or pass the pointer value (EmptyPChar) rather than its address.

Suggested change
StoreIntReg(IPointer(@EmptyPChar))
StoreIntReg(IPointer(EmptyPChar))

Copilot uses AI. Check for mistakes.
{$define empty_methods_handler}
{$ifend}
{$else}
{$if defined(cpuarm)}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Delphi ARM64 (CPUARM64), empty_methods_handler is not defined, so the CPU64 branch below will try to compile x64 inline assembly in MyAllMethodsHandler, which won’t compile on ARM64. Extend the Delphi-side condition to include CPUARM64 (or add an ARM64 implementation) so ARM64 builds don’t attempt to compile the x64 asm handler.

Suggested change
{$if defined(cpuarm)}
{$if defined(cpuarm) or defined(cpuarm64)}

Copilot uses AI. Check for mistakes.
ldp x29, x30, [sp], #64
ret
#ifndef __APPLE__
.size arm64call, .-arm64call
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arm64sysv.S does not emit a .note.GNU-stack section. On Linux/GNU ld this can result in an executable stack marking/warnings for the resulting object. Consider adding a .section .note.GNU-stack,"",@progbits (guarded for non-Apple) to keep stacks non-executable by default.

Suggested change
.size arm64call, .-arm64call
.size arm64call, .-arm64call
.section .note.GNU-stack,"",@progbits

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants