-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix Linux ARM64 build #7051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Fix Linux ARM64 build #7051
Conversation
Key issues during port: * Different ARM64 vararg calling convention between macOS (DarwinPCS) and Linux (AAPCS64). Fixed CALL_ENTRYPOINT_NOASSERT and DECLARE_ARGS_VARARRAY using va_list.__stack from the official ABI - robust and compiler-independent. However, there is also JavascriptStackWalker.cpp which depends on the exact stack layout and magic constants, especially ArgOffsetFromFramePtr, this part is more fragile. * char is unsigned in Linux ARM64 ABI unlike macOS/Win, breaking int8 code. Make sure __int8 (=char) is always prefixed with signed/unsigned. * arm64/*.S files use Microsoft-style ; comments, unsupported by GNU assembler. These were already converted in amd64/*.S files to //, but I propose a simpler fix to just strip them on the fly during the build with sed. * Missing _GetNativeSigSimdContext based on https://github.com/dotnet/runtime/blob/main/src/coreclr/pal/src/thread/context.cpp#L927 * Cpsr register is PState on Linux * wchar_t/char16_t mismatches when building with ICU. For now solved with #define wcslen PAL_wcslen etc in ChakraICU.h. It builds at least, though some Intl tests are failing. Note: binary with JIT crashes - this must be built with ./build.sh --no-jit. cmake already prints a warning that ARM64 JIT is only supported on Windows.
rhuanjl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your work on this; it's certainly good to support another platform.
I've put a few initial comments above though I'm going to want to another slightly more detailed review.
Are you able to sign our Contributor agreement? https://github.com/chakra-core/ChakraCore/blob/master/ContributionAgreement.md
| #include "unicode/upluralrules.h" | ||
| #endif // ifdef WINDOWS10_ICU | ||
|
|
||
| // Use PAL wrappers for Linux arm64 to fix wchar_t/char16_t mismatches. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be needed as pal.h should be doing these defines; needing to add them here suggests the include order is wrong, additionally I'd be surprised if there is anything arm specific about the include order.
Could there be an include order issue with a newer version of clang (vs what we use in CI) or with a different flavour of linux (CI only tests Ubuntu)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my testing, it looks like this needs to go anywhere inside lib/Runtime/Runtime.h between PlatformAgnostic/ChakraPlatform.h and Library/JavascriptFunction.h.
Putting it just before before ChakraPlatform.h (vs immediately after) breaks the build in a spooky action at a distance way:
In file included from /home/ivan/src/ChakraCore/lib/Jsrt/Core/JsrtCore.cpp:20:
/home/ivan/src/ChakraCore/lib/Common/Codex/Utf8Helper.h:223:27: error: no matching function for call to 'wcslen'
223 | [ 7%] Building CXX object lib/Common/DataStructures/CMakeFiles/Chakra.Common.DataStructures.dir/DictionaryStats.cpp.o
sourceString, wcslen(sourceString), destStringPtr, &unused);
| ^~~~~~
/usr/include/wchar.h:247:15: note: candidate function not viable: no known conversion from 'LPCWSTR' (aka 'const char16_t *') to 'const wchar_t *' for 1st argument
247 | extern size_t wcslen (const wchar_t *__s) __THROW __attribute_pure__;
| ^ ~~~~~~~~~~~~~~~~~~
/home/ivan/src/ChakraCore/lib/Jsrt/Core/JsrtCore.cpp:919:18: error: no matching function for call to 'wcslen'
919 | length = wcslen((const char16 *)content);
| ^~~~~~
/usr/include/wchar.h:247:15: note: candidate function not viable: no known conversion from 'const char16 *' (aka 'const char16_t *') to 'const wchar_t *' for 1st argument
247 | extern size_t wcslen (const wchar_t *__s) __THROW __attribute_pure__;
| ^ ~~~~~~~~~~~~~~~~~~
This is probably due to various #pragma once, Pch.h system and headers being generously over-included all over the place. There's probably a right place somewhere deep in pal where this can be properly fixed, but it is hard to identify it when the build behaves in this way.
ChakraPlatform.h also transitively includes ChakraICU.h and redefining wcs* functions before unicode headers breaks the build in STL headers (perhaps libstdc++ issue? macOS/Windows build I suppose default to clang's libc++):
In file included from /home/ivan/src/ChakraCore/lib/Runtime/PlatformAgnostic/Platform/Common/UnicodeText.Common.cpp:8:
In file included from /home/ivan/src/ChakraCore/lib/Runtime/PlatformAgnostic/UnicodeText.h:8:
In file included from /home/ivan/src/ChakraCore/lib/Runtime/PlatformAgnostic/ChakraICU.h:52:
In file included from /usr/include/unicode/ucol.h:1525:
In file included from /usr/lib/gcc/aarch64-linux-gnu/14/../../../../include/c++/14/string_view:48:
In file included from /usr/lib/gcc/aarch64-linux-gnu/14/../../../../include/c++/14/bits/char_traits.h:42:
In file included from /usr/lib/gcc/aarch64-linux-gnu/14/../../../../include/c++/14/bits/postypes.h:40:
In file included from /usr/lib/gcc/aarch64-linux-gnu/14/../../../../include/c++/14/cwchar:44:
/usr/include/wchar.h:130:12: error: exception specification in declaration does not match previous declaration
130 | extern int wcscmp (const wchar_t *__s1, const wchar_t *__s2)
| ^
/home/ivan/src/ChakraCore/lib/Runtime/PlatformAgnostic/ChakraICU.h:14:17: note: expanded from macro 'wcscmp'
14 | #define wcscmp PAL_wcscmp
| ^
/home/ivan/src/ChakraCore/pal/inc/pal.h:6242:23: note: previous declaration is here
6242 | PALIMPORT int __cdecl PAL_wcscmp(const WCHAR*, const WCHAR*);
| ^
Library/JavascriptFunction.h included in Runtime.h needs these wcs* defines already, so they need to be in place before that point:
In file included from /home/ivan/src/ChakraCore/lib/Runtime/Library/JavascriptFunction.cpp:5:
In file included from /home/ivan/src/ChakraCore/lib/Runtime/Library/RuntimeLibraryPch.h:10:
In file included from /home/ivan/src/ChakraCore/lib/Runtime/./Runtime.h:436:
/home/ivan/src/ChakraCore/lib/Runtime/./Library/JavascriptFunction.h:288:12: error: no matching function for call to 'wcscmp'
288 | if(wcscmp(displayName, Js::Constants::AnonymousFunction) == 0)
| ^~~~~~
/usr/include/wchar.h:130:12: note: candidate function not viable: no known conversion from 'PCWSTR' (aka 'const char16_t *') to 'const wchar_t *' for 1st argument
130 | extern int wcscmp (const wchar_t *__s1, const wchar_t *__s2)
| ^ ~~~~~~~~~~~~~~~~~~~
1 error generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I found the culprit in /usr/include/c++/14/cwchar:
$ rg def.*wcs /usr/include/
/usr/include/wchar.h
546:# define wcstol __isoc23_wcstol
547:# define wcstoul __isoc23_wcstoul
...
/usr/include/c++/14/cwchar
// Get rid of those macros defined in <wchar.h> in lieu of real functions.
...
81:#undef mbsrtowcs
100:#undef wcscat
101:#undef wcschr
102:#undef wcscmp
103:#undef wcscoll
...
So, looks like an issue caused by libstd++ in recent Debian versions.
Tested build on Ubuntu 22.04 - it works without these extra defines in ChakraICU.h.
| endif() | ||
|
|
||
| if(CC_TARGETS_ARM64) | ||
| if(CC_TARGET_OS_LINUX) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see CI running before we remove this warning; I think we should be able to add linux on arm to the cirrus CI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I will bring it back.
| // Linux ARM64 uses AAPCS64: first 8 args in x0-x7, rest via stack. | ||
| // Fill x2-x7 with nulls here to force the expected stack layout: | ||
| // [RetAddr] [function] [callInfo] [args...] | ||
| #define CALL_ENTRYPOINT_NOASSERT(entryPoint, function, callInfo, ...) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expect this to break the JavascriptStackWalker, I saw you said it was "fragile", what's the status?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The single hard-coded constant ArgOffsetFromFramePtr that JavascriptStackWalker relies on feels rather fragile to me. For now, I set ArgOffsetFromFramePtr=4 - I see 8 extra bytes of what seems like padding or saved registers. But I'd expect it to break with different compilers and optimization flags by analogy with my experience with DECLARE_ARGS_VARARRAY - at first (before I found va_list.__stack) I was trying to fixing it by trying different offsets in _get_va(), but it just kept breaking between optimized and debug builds.
JavascriptStackWalker in JIT-less builds so far works for me with clang 19, 20, and 22, both optimized and debug builds. Perhaps because it only needs to deal with a few specific call sites in this mode. When JIT is enabled it is crashing, but because JIT wasn't supported on macOS, I didn't investigate these crashes further.
| writeLine("n.toString(2): " + n.toString(2)); | ||
| writeLine("n.toString(16): " + n.toString(16)); | ||
| writeLine("n.toString(25): " + n.toString(25)); | ||
| if (!numberToTestAsString.endsWith('e21')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hit this same issue on macOS ARM, and disabled the test for mac see test/Number/rlexe.xml lines 17-24.
Awkwardly we don't have a mechanism for disabling a test for an OS and CPU combination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll try reenabling that test for macOS then with this change.
Yes, added my name there. |
It builds with clang 19-22 on Debian Trixie, both with and without ICU.
Passes test suite (except Intl tests) and benchmarks - on par with
JIT-less SpiderMonkey (on Mac M4):
Key issues during port:
Different ARM64 vararg calling convention between macOS (DarwinPCS) and Linux (AAPCS64).
Fixed CALL_ENTRYPOINT_NOASSERT and DECLARE_ARGS_VARARRAY using va_list.__stack from the official ABI - robust and compiler-independent.
However, there is also JavascriptStackWalker.cpp which depends on the exact stack layout and magic constants, especially ArgOffsetFromFramePtr, this part is more fragile.
char is unsigned in Linux ARM64 ABI unlike macOS/Win, breaking int8 code. Make sure __int8 (=char) is always prefixed with signed/unsigned.
arm64/*.S files use Microsoft-style ; comments, unsupported by GNU assembler. These were already converted in amd64/*.S files to //, but I propose a simpler fix to just strip them on the fly during the build with sed.
Missing _GetNativeSigSimdContext based on https://github.com/dotnet/runtime/blob/main/src/coreclr/pal/src/thread/context.cpp#L927
Cpsr register is PState on Linux
wchar_t/char16_t mismatches when building with ICU. For now solved with #define wcslen PAL_wcslen etc in ChakraICU.h. It builds at least, though some Intl tests are failing.
Note: binary with JIT crashes - this must be built with ./build.sh --no-jit. cmake already prints a warning that ARM64 JIT is only supported on Windows.
Testing
One test failing in release:
Intl compiles with some failing tests:
build.sh: