Native ARM64 Implementation Using Assembler#295
Native ARM64 Implementation Using Assembler#295superflexible wants to merge 1 commit intoremobjects:masterfrom
Conversation
Native ARM64 Implementation Using Assembler Tested on Linux / ARM64 and macOS / M1
| @@ -0,0 +1,375 @@ | |||
| {$IFDEF CPUARM64} | |||
| {$IFDEF MSWINDOWS} | |||
| {$L arm64win.obj} | |||
There was a problem hiding this comment.
This file does not actually exist
There was a problem hiding this comment.
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} | |||
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
There should be instructions on how to recreate to the .o files, because accepting binaries from a PR seems rather unsafe.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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';
There was a problem hiding this comment.
Yes that's correct. My pull request is only for FPC, which allows it.
| .type arm64call, %function | ||
| arm64call: | ||
| #endif | ||
| stp x29, x30, [sp, #-64]! |
There was a problem hiding this comment.
Perhaps there could be some comments describing the various steps?
Also, does it handle exceptions?
| s := ''; | ||
| end; | ||
|
|
||
| function AlwaysAsVariable(aType: TPSTypeRec): Boolean; |
There was a problem hiding this comment.
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)} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I had to do this to get it to compile at all, but maybe I can separate it out.
There was a problem hiding this comment.
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 fromuPSRuntime.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.incto 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}; |
There was a problem hiding this comment.
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)).
| Items: NativeInt); external {$IFDEF DARWIN}name '_arm64call'{$ENDIF}; | |
| Items: NativeInt); external {$IF DEFINED(DARWIN) OR DEFINED(MACOS)}name '_arm64call'{$IFEND}; |
|
|
||
| procedure arm64call( | ||
| Address: Pointer; | ||
| out _X0: IPointer; |
There was a problem hiding this comment.
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).
| out _X0: IPointer; | |
| var _X0: IPointer; |
| StoreIntReg(IPointer(Cardinal(FVar^.dta^))); | ||
| btPChar: | ||
| if Pointer(fvar^.dta^) = nil then | ||
| StoreIntReg(IPointer(@EmptyPChar)) |
There was a problem hiding this comment.
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.
| StoreIntReg(IPointer(@EmptyPChar)) | |
| StoreIntReg(IPointer(EmptyPChar)) |
| {$define empty_methods_handler} | ||
| {$ifend} | ||
| {$else} | ||
| {$if defined(cpuarm)} |
There was a problem hiding this comment.
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.
| {$if defined(cpuarm)} | |
| {$if defined(cpuarm) or defined(cpuarm64)} |
| ldp x29, x30, [sp], #64 | ||
| ret | ||
| #ifndef __APPLE__ | ||
| .size arm64call, .-arm64call |
There was a problem hiding this comment.
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.
| .size arm64call, .-arm64call | |
| .size arm64call, .-arm64call | |
| .section .note.GNU-stack,"",@progbits |
Native ARM64 Implementation Using Assembler
Tested on Linux / ARM64 and macOS / M1