Skip to content

Commit 613d127

Browse files
committed
arm32: fix toolchain/runtime compatibility and builtins mapping
1 parent 0e16ea0 commit 613d127

3 files changed

Lines changed: 101 additions & 20 deletions

File tree

cmake/toolchain.linux-arm32.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
2424
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
2525
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
2626

27+
set(CMAKE_C_FLAGS_INIT "-mfp16-format=ieee -Wno-psabi")
28+
set(CMAKE_CXX_FLAGS_INIT "-mfp16-format=ieee -Wno-psabi")
29+
2730
# add_custom_command() will make bad decisions about running the command
2831
# when crosscompiling (it won't expand the target into a full path).
2932
# Setting CMAKE_CROSSCOMPILING_EMULATOR to /usr/bin/env tricks it into

src/JITModule.cpp

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,85 @@ namespace Internal {
2929

3030
using std::string;
3131

32+
// On 32-bit targets, LLVM JIT code may reference libgcc helper functions
33+
// that dlsym can't always resolve (e.g. under QEMU, or when the host
34+
// compiler inlined them). We provide wrappers and register them in a
35+
// builtins map that getSymbolAddress consults as a fallback.
36+
3237
#if defined(__GNUC__) && defined(__i386__)
3338
extern "C" unsigned long __udivdi3(unsigned long a, unsigned long b);
39+
40+
static const std::map<std::string, uint64_t> i386_builtins = {
41+
{"__udivdi3", (uintptr_t)&__udivdi3},
42+
};
43+
#endif
44+
45+
// On arm-32, LLVM generates calls to __sync_* libgcc functions for atomic
46+
// operations. We provide wrappers using GCC's __sync_* builtins, guarded
47+
// by the __GCC_HAVE_SYNC_COMPARE_AND_SWAP_N predefined macros.
48+
#if defined(__arm__)
49+
50+
static void halide__sync_synchronize() {
51+
__sync_synchronize();
52+
}
53+
54+
#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1
55+
static uint8_t halide__sync_lock_test_and_set_1(volatile uint8_t *ptr, uint8_t val) {
56+
return __sync_lock_test_and_set(ptr, val);
57+
}
58+
#endif
59+
60+
#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4
61+
static uint32_t halide__sync_fetch_and_add_4(volatile uint32_t *ptr, uint32_t val) {
62+
return __sync_fetch_and_add(ptr, val);
63+
}
64+
static uint32_t halide__sync_fetch_and_sub_4(volatile uint32_t *ptr, uint32_t val) {
65+
return __sync_fetch_and_sub(ptr, val);
66+
}
67+
static uint32_t halide__sync_fetch_and_or_4(volatile uint32_t *ptr, uint32_t val) {
68+
return __sync_fetch_and_or(ptr, val);
69+
}
70+
static uint32_t halide__sync_fetch_and_and_4(volatile uint32_t *ptr, uint32_t val) {
71+
return __sync_fetch_and_and(ptr, val);
72+
}
73+
static uint32_t halide__sync_val_compare_and_swap_4(volatile uint32_t *ptr, uint32_t oldval, uint32_t newval) {
74+
return __sync_val_compare_and_swap(ptr, oldval, newval);
75+
}
3476
#endif
3577

78+
#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8
79+
static uint64_t halide__sync_fetch_and_add_8(volatile uint64_t *ptr, uint64_t val) {
80+
return __sync_fetch_and_add(ptr, val);
81+
}
82+
static uint64_t halide__sync_fetch_and_sub_8(volatile uint64_t *ptr, uint64_t val) {
83+
return __sync_fetch_and_sub(ptr, val);
84+
}
85+
static uint64_t halide__sync_val_compare_and_swap_8(volatile uint64_t *ptr, uint64_t oldval, uint64_t newval) {
86+
return __sync_val_compare_and_swap(ptr, oldval, newval);
87+
}
88+
#endif
89+
90+
static const std::map<std::string, uint64_t> arm32_sync_builtins = {
91+
{"__sync_synchronize", (uintptr_t)&halide__sync_synchronize},
92+
#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1
93+
{"__sync_lock_test_and_set_1", (uintptr_t)&halide__sync_lock_test_and_set_1},
94+
#endif
95+
#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4
96+
{"__sync_fetch_and_add_4", (uintptr_t)&halide__sync_fetch_and_add_4},
97+
{"__sync_fetch_and_sub_4", (uintptr_t)&halide__sync_fetch_and_sub_4},
98+
{"__sync_fetch_and_or_4", (uintptr_t)&halide__sync_fetch_and_or_4},
99+
{"__sync_fetch_and_and_4", (uintptr_t)&halide__sync_fetch_and_and_4},
100+
{"__sync_val_compare_and_swap_4", (uintptr_t)&halide__sync_val_compare_and_swap_4},
101+
#endif
102+
#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8
103+
{"__sync_fetch_and_add_8", (uintptr_t)&halide__sync_fetch_and_add_8},
104+
{"__sync_fetch_and_sub_8", (uintptr_t)&halide__sync_fetch_and_sub_8},
105+
{"__sync_val_compare_and_swap_8", (uintptr_t)&halide__sync_val_compare_and_swap_8},
106+
#endif
107+
};
108+
109+
#endif // defined(__arm__)
110+
36111
#ifdef _WIN32
37112
void *get_symbol_address(const char *s) {
38113
return (void *)GetProcAddress(GetModuleHandle(nullptr), s);
@@ -247,25 +322,6 @@ class HalideJITMemoryManager : public SectionMemoryManager {
247322
}
248323
}
249324
uint64_t result = SectionMemoryManager::getSymbolAddress(name);
250-
#if defined(__GNUC__) && defined(__i386__)
251-
// This is a workaround for an odd corner case (cross-compiling + testing
252-
// Python bindings x86-32 on an x86-64 system): __udivdi3 is a helper function
253-
// that GCC uses to do u64/u64 division on 32-bit systems; it's usually included
254-
// by the linker on these systems as needed. When we JIT, LLVM will include references
255-
// to this call; MCJIT fixes up these references by doing (roughly) dlopen(NULL)
256-
// to look up the symbol. For normal JIT tests, this works fine, as dlopen(NULL)
257-
// finds the test executable, which has the right lookups to locate it inside libHalide.so.
258-
// If, however, we are running a JIT-via-Python test, dlopen(NULL) returns the
259-
// CPython executable... which apparently *doesn't* include this as an exported
260-
// function, so the lookup fails and crashiness ensues. So our workaround here is
261-
// a bit icky, but expedient: check for this name if we can't find it elsewhere,
262-
// and if so, return the one we know should be present. (Obviously, if other runtime
263-
// helper functions of this sort crop up in the future, this should be expanded
264-
// into a "builtins map".)
265-
if (result == 0 && name == "__udivdi3") {
266-
result = (uint64_t)&__udivdi3;
267-
}
268-
#endif
269325
internal_assert(result != 0)
270326
<< "HalideJITMemoryManager: unable to find address for " << name << "\n";
271327
return result;
@@ -420,6 +476,25 @@ void compile_module_impl(
420476
}
421477
}
422478
}
479+
480+
// On 32-bit targets, add libgcc builtins that dlsym can't find.
481+
#if defined(__GNUC__) && defined(__i386__)
482+
for (const auto &[sym_name, sym_addr] : i386_builtins) {
483+
auto name = symbolStringPool->intern(sym_name);
484+
if (!newSymbols.count(name)) {
485+
newSymbols.insert({name, {llvm::orc::ExecutorAddr(sym_addr), JITSymbolFlags::Exported}});
486+
}
487+
}
488+
#endif
489+
#if defined(__arm__)
490+
for (const auto &[sym_name, sym_addr] : arm32_sync_builtins) {
491+
auto name = symbolStringPool->intern(sym_name);
492+
if (!newSymbols.count(name)) {
493+
newSymbols.insert({name, {llvm::orc::ExecutorAddr(sym_addr), JITSymbolFlags::Exported}});
494+
}
495+
}
496+
#endif
497+
423498
err = JIT->getMainJITDylib().define(orc::absoluteSymbols(std::move(newSymbols)));
424499
internal_assert(!err) << llvm::toString(std::move(err)) << "\n";
425500

src/runtime/HalideRuntime.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ extern "C" {
117117
// For now, we say that if >= v12, and compiling on x86 or arm,
118118
// we assume support. This may need revision.
119119
#if defined(__GNUC__) && (__GNUC__ >= 12)
120-
#if defined(__x86_64__) || (defined(__i386__) && (__GNUC__ >= 14) && defined(__SSE2__)) || ((defined(__arm__) || defined(__aarch64__)) && (__GNUC__ >= 13))
120+
#if defined(__x86_64__) || \
121+
(defined(__i386__) && (__GNUC__ >= 14) && defined(__SSE2__)) || \
122+
(defined(__arm__) && (__GNUC__ >= 13) && __ARM_FP16_FORMAT_IEEE) || \
123+
(defined(__aarch64__) && (__GNUC__ >= 13))
121124
#define HALIDE_CPP_COMPILER_HAS_FLOAT16
122125
#endif
123126
#endif

0 commit comments

Comments
 (0)