Segfault in jit tuple deforming on arm64 due to LLVM issue
Hi!
I have an instance that started to consistently crash with segfault or
bus error and most of the generated coredumps had corrupted stacks.
Some salvageable frames showed the error happening within
ExecRunCompiledExpr. Sure enough, running the query with jit disabled
stopped the crashes. The issue happens with the following setup:
Ubuntu jammy on arm64, 30G
postgresql-14 14.12-1.pgdg22.04+1
libllvm15 1:15.0.7-0ubuntu0.22.04.3
I was able to isolate the impacted database the db (pg_dump of the
table was not enough, a base backup had to be used) and reproduce the
issue on a debug build of PostgresSQL. This time, there's no crash but
it was stuck in an infinite loop within jit tuple deforming:
#0 0x0000ec53660aa14c in deform_0_1 ()
#1 0x0000ec53660aa064 in evalexpr_0_0 ()
#2 0x0000ab8f9b322948 in ExecEvalExprSwitchContext
(isNull=0xfffff47c3c87, econtext=0xab8fd0f13878, state=0xab8fd0f13c50)
at executor/./build/../src/include/executor/executor.h:342
#3 ExecProject (projInfo=0xab8fd0f13c48) at
executor/./build/../src/include/executor/executor.h:376
Looking at the generated assembly, the infinite loop happens between
deform_0_1+140 and deform_0_1+188
// Store address page in x11 register
0xec53660aa130 <deform_0_1+132> adrp x11, 0xec53fd308000
// Start of the infinite loop
0xec53660aa138 <deform_0_1+140> adr x8, 0xec53660aa138 <deform_0_1+140>
// Load the content of 0xec53fd308000[x12] in x10, x12 was 0 at that time
0xec53660aa13c <deform_0_1+144> ldrsw x10, [x11, x12, lsl #2]
// Add the loaded offset to x8
0xec53660aa140 <deform_0_1+148> add x8, x8, x10
...
// Branch to address in x8. Since x10 was 0, x8 has the value
deform_0_1+140, creating the infinite loop
0xec53660aa168 <deform_0_1+188> br x8
Looking at the content of 0xec53fd308000, We only see 0 values stored
at the address.
x/6 0xec53fd308000
0xec53fd308000: 0x00000000 0x00000000 0x00000000 0x00000000
0xec53fd308010: 0x00000000 0x00000000
The assembly matches the code for the find_start switch case in
llvmjit_deform[1]https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/jit/llvm/llvmjit_deform.c#L364-L382. The content at the address 0xec53fd308000 should
contain the offset table from the PC to branch to the correct
attcheckattnoblocks block. As a comparison, if I execute a query not
impacted by the issue (the size of the jit compiled module seems to be
a factor), I can see that the offset table was correctly filled.
x/6 0xec55fd30700
0xec55fd307000: 0x00000060 0x00000098 0x000000e8 0x00000170
0xec55fd307010: 0x0000022c 0x000002e8
I was suspecting something was erasing the content of the offset table
so I've checked with rr. However, it was only initialized and nothing
was written at this memory address. I was starting to suspect a
possible LLVM issue and ran the query against a debug build of
llvm_jit. It immediately triggered the following assertion[2]https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp#L501-L513:
void llvm::RuntimeDyldELF::resolveAArch64Relocation(const
llvm::SectionEntry &, uint64_t, uint64_t, uint32_t, int64_t):
Assertion `isInt<33>(Result) && "overflow check failed for
relocation"' failed.
This happens when LLVM is resolving relocations.
#5 __GI___assert_fail (assertion=0xf693f214771a "isInt<33>(Result) &&
\"overflow check failed for relocation\"", file=0xf693f2147269
"/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp",
line=507, function=0xf693f214754f "void
llvm::RuntimeDyldELF::resolveAArch64Relocation(const
llvm::SectionEntry &, uint64_t, uint64_t, uint32_t, int64_t)") at
./assert/assert.c:101
#6 llvm::RuntimeDyldELF::resolveAArch64Relocation () at
/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp:507
#7 llvm::RuntimeDyldELF::resolveRelocation () at
/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp:1044
#8 llvm::RuntimeDyldELF::resolveRelocation () at
/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp:1026
#9 llvm::RuntimeDyldImpl::resolveRelocationList () at
/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp:1112
#10 llvm::RuntimeDyldImpl::resolveLocalRelocations () at
/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp:157
#11 llvm::RuntimeDyldImpl::finalizeAsync() at
/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp:1247
During the assertion failure, I have the following values:
Value: 0xfbc84fab9000
FinalAddress: 0xfbc5b9cea12c
Addend: 0x0
Result: 0x295dcf000
The result is indeed greater than an int32, triggering the assert.
Looking at the sections created by LLVM in allocateSection[3]https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/SectionMemoryManager.cpp#L41C32-L41C47, we have
3 sections created:
.text {Address = 0xfbc5b9cea000, AllocatedSize = 90112}
.rodata {Address = 0xfbc84fab9000, AllocatedSize = 4096}
.eh_frame {Address = 0xfbc84fab7000, AllocatedSize = 8192}
When resolving relocation, the difference between the rodata section
and the PC is computed and stored in the ADRP instruction. However,
when a new section is allocated, LLVM will request a new memory block
from the memory allocator[4]https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/SectionMemoryManager.cpp#L94-L110. The MemGroup.Near is passed as the start
hint of mmap but that's only a hint and the kernel doesn't provide any
guarantee that the new allocated block will be near. With the impacted
query, there are more than 10GB of gap between the .text section and
the .rodata section, making it impossible for the code in the .text
section to correctly fetch data from the .rodata section as the
address in ADRP is limited to a +/-4GB range.
There are mentions about this in the ABI that the GOT section should
be within 4GB from the text section[5]https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst#7code-models. Though in this case, there's
no GOT section as the offsets are stored in the .rodata section but
the constraint is going to be similar. This is a known LLVM issue[6]https://github.com/llvm/llvm-project/issues/71963
that impacted Impala, Numba and Julia. There's an open PR[7]https://github.com/llvm/llvm-project/pull/71968 to fix
the issue by allocating all sections as a single memory block,
avoiding the gaps between sections. There's also a related discussion
on this on llvm-rtdyld discourse[8]https://discourse.llvm.org/t/llvm-rtdyld-aarch64-abi-relocation-restrictions/74616.
A possible mitigation is to switch from RuntimeDyld to JITLinking but
this requires at least LLVM15 as LLVM14 doesn't have any significant
relocation support for aarch64[9]https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp#L75-L84. I did test using JITLinking on my
impacted db and it seems to fix the issue. JITLinking has no exposed C
interface though so it requires additional wrapping.
I don't necessarily have a good answer for this issue. I've tried to
tweak relocation settings or the jit code to avoid relocation without
too much success. Ideally, the llvm fix will be merged and backported
in llvm but the PR has been open for some time now. I've seen multiple
segfault reports that look similar to this issue (example: [10]/messages/by-id/CABa+nRvwZy_5t1QF9NJNGwAf03tv_PO_Sg1FsN1+-3Odb1XgBA@mail.gmail.com, [11]/messages/by-id/CADAf1kavcN-kY=vEm3MYxhUa+rtGFs7tym5d7Ee6Ni2cwwxGqQ@mail.gmail.com)
but I don't think it was linked to the LLVM bug so I figured I would
at least share my findings.
[1]: https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/jit/llvm/llvmjit_deform.c#L364-L382
[2]: https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp#L501-L513
[3]: https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/SectionMemoryManager.cpp#L41C32-L41C47
[4]: https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/SectionMemoryManager.cpp#L94-L110
[5]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst#7code-models
[6]: https://github.com/llvm/llvm-project/issues/71963
[7]: https://github.com/llvm/llvm-project/pull/71968
[8]: https://discourse.llvm.org/t/llvm-rtdyld-aarch64-abi-relocation-restrictions/74616
[9]: https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp#L75-L84
[10]: /messages/by-id/CABa+nRvwZy_5t1QF9NJNGwAf03tv_PO_Sg1FsN1+-3Odb1XgBA@mail.gmail.com
[11]: /messages/by-id/CADAf1kavcN-kY=vEm3MYxhUa+rtGFs7tym5d7Ee6Ni2cwwxGqQ@mail.gmail.com
Regards,
Anthonin Bonnefoy
On Thu, Aug 22, 2024 at 7:22 PM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:
Ideally, the llvm fix will be merged and backported
in llvm but the PR has been open for some time now.
I fear that back-porting, for the LLVM project, would mean "we fix it
in main/20.x, and also back-port it to 19.x". Do distros back-port
further?
Nice detective work!
The JITLINK change sounds interesting, and like something we need to
do sooner or later.
On Thu, Aug 22, 2024 at 12:33 PM Thomas Munro <thomas.munro@gmail.com> wrote:
I fear that back-porting, for the LLVM project, would mean "we fix it
in main/20.x, and also back-port it to 19.x". Do distros back-port
further?
That's also my fear, I'm not familiar with distros back-port policy
but eyeballing ubuntu package changelog[1]https://launchpad.net/ubuntu/+source/llvm-toolchain-16/+changelog, it seems to be mostly
build fixes.
Given that there's no visible way to fix the relocation issue, I
wonder if jit shouldn't be disabled for arm64 until either the
RuntimeDyld fix is merged or the switch to JITLink is done. Disabling
jit tuple deforming may be enough but I'm not confident the issue
won't happen in a different part.
[1]: https://launchpad.net/ubuntu/+source/llvm-toolchain-16/+changelog
On Sat, Aug 24, 2024 at 12:22 AM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:
On Thu, Aug 22, 2024 at 12:33 PM Thomas Munro <thomas.munro@gmail.com> wrote:
I fear that back-porting, for the LLVM project, would mean "we fix it
in main/20.x, and also back-port it to 19.x". Do distros back-port
further?That's also my fear, I'm not familiar with distros back-port policy
but eyeballing ubuntu package changelog[1], it seems to be mostly
build fixes.Given that there's no visible way to fix the relocation issue, I
wonder if jit shouldn't be disabled for arm64 until either the
RuntimeDyld fix is merged or the switch to JITLink is done. Disabling
jit tuple deforming may be enough but I'm not confident the issue
won't happen in a different part.
We've experienced something a little similar before: In the early days
of PostgreSQL LLVM, it didn't work at all on ARM or POWER. We sent a
trivial fix[1]/messages/by-id/CAEepm=39F_B3Ou8S3OrUw+hJEUP3p=wCu0ug-TTW67qKN53g3w@mail.gmail.com upstream that landed in LLVM 7; since it was a small
and obvious problem and it took a long time for some distros to ship
LLVM 7, we even contemplated hot-patching that LLVM function with our
own copy (but, ugh, only for about 7 nanoseconds). That was before we
turned JIT on by default, and was also easier to deal with because it
was an obvious consistent failure in basic tests, so packagers
probably just disabled the build option on those architectures. IIUC
this one is a random and rare crash depending on malloc() and perhaps
also the working size of your virtual memory dart board. (Annoyingly,
I had tried to reproduce this quite a few times on small ARM systems
when earlier reports came in, d'oh!).
This degree of support window mismatch is probably what triggered RHEL
to develop their new rolling LLVM version policy. Unfortunately, it's
the other distros that tell *us* which versions to support, and not
the reverse (for example CF #4920 is about to drop support for LLVM <
14, but that will only be for PostgreSQL 18+).
Ultimately, if it doesn't work, and doesn't get fixed, it's hard for
us to do much about it. But hmm, this is probably madness... I wonder
if it would be feasible to detect address span overflow ourselves at a
useful time, as a kind of band-aid defence...
[1]: /messages/by-id/CAEepm=39F_B3Ou8S3OrUw+hJEUP3p=wCu0ug-TTW67qKN53g3w@mail.gmail.com
On Mon, Aug 26, 2024 at 4:33 AM Thomas Munro <thomas.munro@gmail.com> wrote:
IIUC this one is a random and rare crash depending on malloc() and
perhaps also the working size of your virtual memory dart board.
(Annoyingly, I had tried to reproduce this quite a few times on small ARM
systems when earlier reports came in, d'oh!).
allocateMappedMemory used when creating sections will eventually call
mmap[1]https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/Support/Unix/Memory.inc#L115-L117, not malloc. So the amount of shared memory configured may be
a factor in triggering the issue.
My first attempts to reproduce the issue from scratch weren't
successful either. However, trying again with different values of
shared_buffers, I've managed to trigger the issue somewhat reliably.
On a clean Ubuntu jammy, I've compiled the current PostgreSQL
REL_14_STABLE (6bc2bfc3) with the following options:
CLANG=clang-14 ../configure --enable-cassert --enable-debug --prefix
~/.local/ --with-llvm
Set "shared_buffers = '4GB'" in the configuration. More may be needed
but 4GB was enough for me.
Create a table with multiple partitions with pgbench. The goal is to
have a jit module big enough to trigger the issue.
pgbench -i --partitions=64
Then run the following query with jit forcefully enabled:
psql options=-cjit_above_cost=0 -c 'SELECT count(bid) from pgbench_accounts;'
If the issue was successfully triggered, it should segfault or be
stuck in an infinite loop.
Ultimately, if it doesn't work, and doesn't get fixed, it's hard for
us to do much about it. But hmm, this is probably madness... I wonder
if it would be feasible to detect address span overflow ourselves at a
useful time, as a kind of band-aid defence...
There's a possible alternative, but it's definitely in the same
category as the hot-patching idea. llvmjit uses
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager to
create the ObjectLinkingLayer and it will be created with the default
SectionMemoryManager[2]https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/Orc/OrcV2CBindings.cpp#L967-L973. It should be possible to provide a modified
SectionMemoryManager with the change to allocate sections in a single
block and it could be restricted to arm64 architecture. A part of me
tells me this is probably a bad idea but on the other hand, LLVM
provides this way to plug a custom allocator and it would fix the
issue...
[1]: https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/Support/Unix/Memory.inc#L115-L117
[2]: https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/Orc/OrcV2CBindings.cpp#L967-L973
On Tue, Aug 27, 2024 at 2:16 AM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:
There's a possible alternative, but it's definitely in the same
category as the hot-patching idea. llvmjit uses
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager to
create the ObjectLinkingLayer and it will be created with the default
SectionMemoryManager[2]. It should be possible to provide a modified
SectionMemoryManager with the change to allocate sections in a single
block and it could be restricted to arm64 architecture. A part of me
tells me this is probably a bad idea but on the other hand, LLVM
provides this way to plug a custom allocator and it would fix the
issue...
Interesting. Here is a quick hack to experiment with injecting a new
memory manager. This one just wraps the normal one and logs the
addresses it allocates, but from here, you're right, we could try to
constraint its address range somehow (or perhaps just check its range
and fail gracefully).
Attachments:
0001-XXX-replace-LLVM-SectionMemoryManager.patchapplication/octet-stream; name=0001-XXX-replace-LLVM-SectionMemoryManager.patchDownload
From 17e3979bd2cad997c853ea6ceae5879ac977e014 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: [PATCH] XXX replace LLVM SectionMemoryManager
---
src/backend/jit/llvm/llvmjit.c | 3 ++-
src/backend/jit/llvm/llvmjit_wrap.cpp | 23 +++++++++++++++++++++++
src/include/jit/llvmjit.h | 2 ++
3 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 0f6cec52496..23803172575 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -1279,7 +1279,8 @@ static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
LLVMOrcObjectLayerRef objlayer =
- LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 641c8841ca3..f4a35325482 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -17,11 +17,15 @@ extern "C"
}
#include <llvm-c/Core.h>
+#include <llvm-c/OrcEE.h>
/* Avoid macro clash with LLVM's C++ headers */
#undef Min
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#include <llvm/IR/Function.h>
+#include <llvm/Support/CBindingWrapping.h>
#include "jit/llvmjit.h"
@@ -41,3 +45,22 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+struct SafeSectionMemoryManager : llvm::SectionMemoryManager {
+ uint8_t *allocateCodeSection (uintptr_t Size, unsigned Alignment, unsigned SectionID, llvm::StringRef SectionName) override {
+ uint8_t *result;
+ result = SectionMemoryManager::allocateCodeSection(Size, Alignment, SectionID, SectionName);
+ elog(NOTICE, "allocateCodeSection returning %p", result);
+ return result;
+ }
+};
+
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<SafeSectionMemoryManager>(); }));
+}
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..99c6fd15849 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -18,6 +18,7 @@
#ifdef USE_LLVM
#include <llvm-c/Types.h>
+#include <llvm-c/OrcEE.h>
/*
@@ -135,6 +136,7 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef RS);
#ifdef __cplusplus
} /* extern "C" */
--
2.39.3 (Apple Git-146)
Here is an experimental attempt to steal the SectorMemoryManager from
https://github.com/llvm/llvm-project/pull/71968, rename it to
SafeSectorMemoryManager, and inject it as shown in the previous patch.
Another approach might be to try to make a new class that derives from
SectorMemoryManager and adjusts minimal bits and pieces, but I figured
it would be easier to diff against their code if we take the whole
file. Hmm, I guess if "diff" convenience is the driving factor, it
might be better to use a different namespace instead of a different
name...
I am sure this requires changes for various LLVM versions. I tested
it with LLVM 14 on a Mac where I've never managed to reproduce the
original complaint, but ... ooooh, this might be exacerbated by ASLR,
and macOS only has a small ALSR slide window (16M or 256M apparently,
according to me in another thread), so I'd probably have to interpose
my own mmap() to choose some more interesting addresses, or run some
other OS, but that's quite enough rabbit holes for one morning.
Attachments:
v2-0001-XXX-LLVM-ARM-relocation-bug-mitigation.patchapplication/octet-stream; name=v2-0001-XXX-LLVM-ARM-relocation-bug-mitigation.patchDownload
From 43670431d5aed4a19598bc0e0b4c601eb9fe3773 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: [PATCH v2] XXX LLVM ARM relocation bug mitigation.
The patched code from https://github.com/llvm/llvm-project/pull/71968,
moved into a new class SafeSectionMemoryManager, adjusted to work on LLVM < 16,
and used in place of the regular memory manager.
XXX experimental
XXX this may be a terrible idea
XXX several details include #include directives would need adjustment
for prehistoric LLVM versions
Reported-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Discussion: https://postgr.es/m/CAO6_Xqr63qj%3DSx7HY6ZiiQ6R_JbX%2B-p6sTPwDYwTWZjUmjsYBg%40mail.gmail.com
---
src/backend/jit/llvm/Makefile | 3 +-
.../jit/llvm/SafeSectionMemoryManager.cpp | 386 ++++++++++++++++++
src/backend/jit/llvm/llvmjit.c | 3 +-
src/backend/jit/llvm/llvmjit_wrap.cpp | 20 +
src/backend/jit/llvm/meson.build | 1 +
src/include/jit/SafeSectionMemoryManager.h | 225 ++++++++++
src/include/jit/llvmjit.h | 2 +
7 files changed, 638 insertions(+), 2 deletions(-)
create mode 100644 src/backend/jit/llvm/SafeSectionMemoryManager.cpp
create mode 100644 src/include/jit/SafeSectionMemoryManager.h
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
index bfe5c207a2b..d7aeaff4472 100644
--- a/src/backend/jit/llvm/Makefile
+++ b/src/backend/jit/llvm/Makefile
@@ -47,7 +47,8 @@ OBJS += \
llvmjit.o \
llvmjit_error.o \
llvmjit_inline.o \
- llvmjit_wrap.o
+ llvmjit_wrap.o \
+ SafeSectionMemoryManager.o
# Code generation
OBJS += \
diff --git a/src/backend/jit/llvm/SafeSectionMemoryManager.cpp b/src/backend/jit/llvm/SafeSectionMemoryManager.cpp
new file mode 100644
index 00000000000..88d7f43f621
--- /dev/null
+++ b/src/backend/jit/llvm/SafeSectionMemoryManager.cpp
@@ -0,0 +1,386 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to SafeSectionMemoryManager, so we can support the ARM
+ * memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the section-based memory manager used by the MCJIT
+// execution engine and RuntimeDyld
+//
+//===----------------------------------------------------------------------===//
+
+#include "jit/SafeSectionMemoryManager.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Process.h"
+
+namespace llvm {
+
+bool SafeSectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
+ uintptr_t Size) const {
+ for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= Size)
+ return true;
+ }
+ return false;
+}
+
+#if LLVM_VERSION_MAJOR < 16
+void SafeSectionMemoryManager::reserveAllocationSpace(uintptr_t CodeSize,
+ uint32_t CodeAlign_i,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign_i,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign_i) {
+ Align CodeAlign(CodeAlign_i);
+ Align RODataAlign(RODataAlign_i);
+ Align RWDataAlign(RWDataAlign_i);
+#else
+void SafeSectionMemoryManager::reserveAllocationSpace(
+ uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
+ Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
+#endif
+ if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
+ return;
+
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ // Code alignment needs to be at least the stub alignment - however, we
+ // don't have an easy way to get that here so as a workaround, we assume
+ // it's 8, which is the largest value I observed across all platforms.
+ constexpr uint64_t StubAlign = 8;
+ CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
+ RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
+ RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
+
+ // Get space required for each section. Use the same calculation as
+ // allocateSection because we need to be able to satisfy it.
+ uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
+ uint64_t RequiredRODataSize =
+ alignTo(RODataSize, RODataAlign) + RODataAlign.value();
+ uint64_t RequiredRWDataSize =
+ alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
+
+ if (hasSpace(CodeMem, RequiredCodeSize) &&
+ hasSpace(RODataMem, RequiredRODataSize) &&
+ hasSpace(RWDataMem, RequiredRWDataSize)) {
+ // Sufficient space in contiguous block already available.
+ return;
+ }
+
+ // MemoryManager does not have functions for releasing memory after it's
+ // allocated. Normally it tries to use any excess blocks that were allocated
+ // due to page alignment, but if we have insufficient free memory for the
+ // request this can lead to allocating disparate memory that can violate the
+ // ARM ABI. Clear free memory so only the new allocations are used, but do
+ // not release allocated memory as it may still be in-use.
+ CodeMem.FreeMem.clear();
+ RODataMem.FreeMem.clear();
+ RWDataMem.FreeMem.clear();
+
+ // Round up to the nearest page size. Blocks must be page-aligned.
+ RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
+ RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
+ RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
+ uint64_t RequiredSize =
+ RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
+
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ AllocationPurpose::RWData, RequiredSize, nullptr,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ return;
+ }
+ // CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
+ CodeMem.AllocatedMem.push_back(MB);
+ uintptr_t Addr = (uintptr_t)MB.base();
+ FreeMemBlock FreeMB;
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+
+ if (CodeSize > 0) {
+ assert(isAddrAligned(CodeAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
+ CodeMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredCodeSize;
+ }
+
+ if (RODataSize > 0) {
+ assert(isAddrAligned(RODataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
+ RODataMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredRODataSize;
+ }
+
+ if (RWDataSize > 0) {
+ assert(isAddrAligned(RWDataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
+ RWDataMem.FreeMem.push_back(FreeMB);
+ }
+}
+
+uint8_t *SafeSectionMemoryManager::allocateDataSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName,
+ bool IsReadOnly) {
+ if (IsReadOnly)
+ return allocateSection(SafeSectionMemoryManager::AllocationPurpose::ROData,
+ Size, Alignment);
+ return allocateSection(SafeSectionMemoryManager::AllocationPurpose::RWData, Size,
+ Alignment);
+}
+
+uint8_t *SafeSectionMemoryManager::allocateCodeSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) {
+ return allocateSection(SafeSectionMemoryManager::AllocationPurpose::Code, Size,
+ Alignment);
+}
+
+uint8_t *SafeSectionMemoryManager::allocateSection(
+ SafeSectionMemoryManager::AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment) {
+ if (!Alignment)
+ Alignment = 16;
+
+ assert(!(Alignment & (Alignment - 1)) && "Alignment must be a power of two.");
+
+ uintptr_t RequiredSize = Alignment * ((Size + Alignment - 1) / Alignment + 1);
+ uintptr_t Addr = 0;
+
+ MemoryGroup &MemGroup = [&]() -> MemoryGroup & {
+ switch (Purpose) {
+ case AllocationPurpose::Code:
+ return CodeMem;
+ case AllocationPurpose::ROData:
+ return RODataMem;
+ case AllocationPurpose::RWData:
+ return RWDataMem;
+ }
+ llvm_unreachable("Unknown SafeSectionMemoryManager::AllocationPurpose");
+ }();
+
+ // Look in the list of free memory regions and use a block there if one
+ // is available.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= RequiredSize) {
+ Addr = (uintptr_t)FreeMB.Free.base();
+ uintptr_t EndOfBlock = Addr + FreeMB.Free.allocatedSize();
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ if (FreeMB.PendingPrefixIndex == (unsigned)-1) {
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // Remember this pending block, such that future allocations can just
+ // modify it rather than creating a new one
+ FreeMB.PendingPrefixIndex = MemGroup.PendingMem.size() - 1;
+ } else {
+ sys::MemoryBlock &PendingMB =
+ MemGroup.PendingMem[FreeMB.PendingPrefixIndex];
+ PendingMB = sys::MemoryBlock(PendingMB.base(),
+ Addr + Size - (uintptr_t)PendingMB.base());
+ }
+
+ // Remember how much free space is now left in this block
+ FreeMB.Free =
+ sys::MemoryBlock((void *)(Addr + Size), EndOfBlock - Addr - Size);
+ return (uint8_t *)Addr;
+ }
+ }
+
+ // No pre-allocated free block was large enough. Allocate a new memory region.
+ // Note that all sections get allocated as read-write. The permissions will
+ // be updated later based on memory group.
+ //
+ // FIXME: It would be useful to define a default allocation size (or add
+ // it as a constructor parameter) to minimize the number of allocations.
+ //
+ // FIXME: Initialize the Near member for each memory group to avoid
+ // interleaving.
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ Purpose, RequiredSize, &MemGroup.Near,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ // FIXME: Add error propagation to the interface.
+ return nullptr;
+ }
+
+ // Save this address as the basis for our next request
+ MemGroup.Near = MB;
+
+ // Copy the address to all the other groups, if they have not
+ // been initialized.
+ if (CodeMem.Near.base() == nullptr)
+ CodeMem.Near = MB;
+ if (RODataMem.Near.base() == nullptr)
+ RODataMem.Near = MB;
+ if (RWDataMem.Near.base() == nullptr)
+ RWDataMem.Near = MB;
+
+ // Remember that we allocated this memory
+ MemGroup.AllocatedMem.push_back(MB);
+ Addr = (uintptr_t)MB.base();
+ uintptr_t EndOfBlock = Addr + MB.allocatedSize();
+
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // The allocateMappedMemory may allocate much more memory than we need. In
+ // this case, we store the unused memory as a free memory block.
+ unsigned FreeSize = EndOfBlock - Addr - Size;
+ if (FreeSize > 16) {
+ FreeMemBlock FreeMB;
+ FreeMB.Free = sys::MemoryBlock((void *)(Addr + Size), FreeSize);
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ MemGroup.FreeMem.push_back(FreeMB);
+ }
+
+ // Return aligned address
+ return (uint8_t *)Addr;
+}
+
+bool SafeSectionMemoryManager::finalizeMemory(std::string *ErrMsg) {
+ // FIXME: Should in-progress permissions be reverted if an error occurs?
+ std::error_code ec;
+
+ // Make code memory executable.
+ ec = applyMemoryGroupPermissions(CodeMem,
+ sys::Memory::MF_READ | sys::Memory::MF_EXEC);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Make read-only data memory read-only.
+ ec = applyMemoryGroupPermissions(RODataMem, sys::Memory::MF_READ);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Read-write data memory already has the correct permissions
+
+ // Some platforms with separate data cache and instruction cache require
+ // explicit cache flush, otherwise JIT code manipulations (like resolved
+ // relocations) will get to the data cache but not to the instruction cache.
+ invalidateInstructionCache();
+
+ return false;
+}
+
+static sys::MemoryBlock trimBlockToPageSize(sys::MemoryBlock M) {
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ size_t StartOverlap =
+ (PageSize - ((uintptr_t)M.base() % PageSize)) % PageSize;
+
+ size_t TrimmedSize = M.allocatedSize();
+ TrimmedSize -= StartOverlap;
+ TrimmedSize -= TrimmedSize % PageSize;
+
+ sys::MemoryBlock Trimmed((void *)((uintptr_t)M.base() + StartOverlap),
+ TrimmedSize);
+
+ assert(((uintptr_t)Trimmed.base() % PageSize) == 0);
+ assert((Trimmed.allocatedSize() % PageSize) == 0);
+ assert(M.base() <= Trimmed.base() &&
+ Trimmed.allocatedSize() <= M.allocatedSize());
+
+ return Trimmed;
+}
+
+std::error_code
+SafeSectionMemoryManager::applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions) {
+ for (sys::MemoryBlock &MB : MemGroup.PendingMem)
+ if (std::error_code EC = MMapper->protectMappedMemory(MB, Permissions))
+ return EC;
+
+ MemGroup.PendingMem.clear();
+
+ // Now go through free blocks and trim any of them that don't span the entire
+ // page because one of the pending blocks may have overlapped it.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ FreeMB.Free = trimBlockToPageSize(FreeMB.Free);
+ // We cleared the PendingMem list, so all these pointers are now invalid
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ }
+
+ // Remove all blocks which are now empty
+ erase_if(MemGroup.FreeMem, [](FreeMemBlock &FreeMB) {
+ return FreeMB.Free.allocatedSize() == 0;
+ });
+
+ return std::error_code();
+}
+
+void SafeSectionMemoryManager::invalidateInstructionCache() {
+ for (sys::MemoryBlock &Block : CodeMem.PendingMem)
+ sys::Memory::InvalidateInstructionCache(Block.base(),
+ Block.allocatedSize());
+}
+
+SafeSectionMemoryManager::~SafeSectionMemoryManager() {
+ for (MemoryGroup *Group : {&CodeMem, &RWDataMem, &RODataMem}) {
+ for (sys::MemoryBlock &Block : Group->AllocatedMem)
+ MMapper->releaseMappedMemory(Block);
+ }
+}
+
+SafeSectionMemoryManager::MemoryMapper::~MemoryMapper() = default;
+
+void SafeSectionMemoryManager::anchor() {}
+
+namespace {
+// Trivial implementation of SectionMemoryManager::MemoryMapper that just calls
+// into sys::Memory.
+class DefaultMMapper final : public SafeSectionMemoryManager::MemoryMapper {
+public:
+ sys::MemoryBlock
+ allocateMappedMemory(SafeSectionMemoryManager::AllocationPurpose Purpose,
+ size_t NumBytes, const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) override {
+ return sys::Memory::allocateMappedMemory(NumBytes, NearBlock, Flags, EC);
+ }
+
+ std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) override {
+ return sys::Memory::protectMappedMemory(Block, Flags);
+ }
+
+ std::error_code releaseMappedMemory(sys::MemoryBlock &M) override {
+ return sys::Memory::releaseMappedMemory(M);
+ }
+};
+} // namespace
+
+SafeSectionMemoryManager::SafeSectionMemoryManager(MemoryMapper *UnownedMM,
+ bool ReserveAlloc)
+ : MMapper(UnownedMM), OwnedMMapper(nullptr),
+ ReserveAllocation(ReserveAlloc) {
+ if (!MMapper) {
+ OwnedMMapper = std::make_unique<DefaultMMapper>();
+ MMapper = OwnedMMapper.get();
+ }
+}
+
+} // namespace llvm
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 0f6cec52496..23803172575 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -1279,7 +1279,8 @@ static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
LLVMOrcObjectLayerRef objlayer =
- LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 641c8841ca3..bf5e2229a82 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -17,13 +17,18 @@ extern "C"
}
#include <llvm-c/Core.h>
+#include <llvm-c/OrcEE.h>
/* Avoid macro clash with LLVM's C++ headers */
#undef Min
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#include <llvm/IR/Function.h>
+#include <llvm/Support/CBindingWrapping.h>
#include "jit/llvmjit.h"
+#include "jit/SafeSectionMemoryManager.h"
/*
@@ -41,3 +46,18 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+#if __arch64__
+ const static bool reserve = true;
+#else
+ const static bool reserve = false;
+#endif
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<llvm::SafeSectionMemoryManager>(nullptr, reserve); }));
+}
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index 4a4232661ba..b76e2e70547 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -14,6 +14,7 @@ llvmjit_sources += files(
'llvmjit_error.cpp',
'llvmjit_inline.cpp',
'llvmjit_wrap.cpp',
+ 'SectionMemoryManager.cpp',
)
# Code generation
diff --git a/src/include/jit/SafeSectionMemoryManager.h b/src/include/jit/SafeSectionMemoryManager.h
new file mode 100644
index 00000000000..985e85440aa
--- /dev/null
+++ b/src/include/jit/SafeSectionMemoryManager.h
@@ -0,0 +1,225 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to SafeSectionMemoryManager, so we can support the ARM
+ * memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.h - Memory manager for MCJIT/RtDyld -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of a section-based memory manager used by
+// the MCJIT execution engine and RuntimeDyld.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_SAFESECTIONMEMORYMANAGER_H
+#define LLVM_EXECUTIONENGINE_SAFESECTIONMEMORYMANAGER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Memory.h"
+#include <cstdint>
+#include <string>
+#include <system_error>
+
+namespace llvm {
+
+/// This is a simple memory manager which implements the methods called by
+/// the RuntimeDyld class to allocate memory for section-based loading of
+/// objects, usually those generated by the MCJIT execution engine.
+///
+/// This memory manager allocates all section memory as read-write. The
+/// RuntimeDyld will copy JITed section memory into these allocated blocks
+/// and perform any necessary linking and relocations.
+///
+/// Any client using this memory manager MUST ensure that section-specific
+/// page permissions have been applied before attempting to execute functions
+/// in the JITed object. Permissions can be applied either by calling
+/// MCJIT::finalizeObject or by calling SectionMemoryManager::finalizeMemory
+/// directly. Clients of MCJIT should call MCJIT::finalizeObject.
+class SafeSectionMemoryManager : public RTDyldMemoryManager {
+public:
+ /// This enum describes the various reasons to allocate pages from
+ /// allocateMappedMemory.
+ enum class AllocationPurpose {
+ Code,
+ ROData,
+ RWData,
+ };
+
+ /// Implementations of this interface are used by SectionMemoryManager to
+ /// request pages from the operating system.
+ class MemoryMapper {
+ public:
+ /// This method attempts to allocate \p NumBytes bytes of virtual memory for
+ /// \p Purpose. \p NearBlock may point to an existing allocation, in which
+ /// case an attempt is made to allocate more memory near the existing block.
+ /// The actual allocated address is not guaranteed to be near the requested
+ /// address. \p Flags is used to set the initial protection flags for the
+ /// block of the memory. \p EC [out] returns an object describing any error
+ /// that occurs.
+ ///
+ /// This method may allocate more than the number of bytes requested. The
+ /// actual number of bytes allocated is indicated in the returned
+ /// MemoryBlock.
+ ///
+ /// The start of the allocated block must be aligned with the system
+ /// allocation granularity (64K on Windows, page size on Linux). If the
+ /// address following \p NearBlock is not so aligned, it will be rounded up
+ /// to the next allocation granularity boundary.
+ ///
+ /// \r a non-null MemoryBlock if the function was successful, otherwise a
+ /// null MemoryBlock with \p EC describing the error.
+ virtual sys::MemoryBlock
+ allocateMappedMemory(AllocationPurpose Purpose, size_t NumBytes,
+ const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) = 0;
+
+ /// This method sets the protection flags for a block of memory to the state
+ /// specified by \p Flags. The behavior is not specified if the memory was
+ /// not allocated using the allocateMappedMemory method.
+ /// \p Block describes the memory block to be protected.
+ /// \p Flags specifies the new protection state to be assigned to the block.
+ ///
+ /// If \p Flags is MF_WRITE, the actual behavior varies with the operating
+ /// system (i.e. MF_READ | MF_WRITE on Windows) and the target architecture
+ /// (i.e. MF_WRITE -> MF_READ | MF_WRITE on i386).
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) = 0;
+
+ /// This method releases a block of memory that was allocated with the
+ /// allocateMappedMemory method. It should not be used to release any memory
+ /// block allocated any other way.
+ /// \p Block describes the memory to be released.
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code releaseMappedMemory(sys::MemoryBlock &M) = 0;
+
+ virtual ~MemoryMapper();
+ };
+
+ /// Creates a SectionMemoryManager instance with \p MM as the associated
+ /// memory mapper. If \p MM is nullptr then a default memory mapper is used
+ /// that directly calls into the operating system.
+ ///
+ /// If \p ReserveAlloc is true all memory will be pre-allocated, and any
+ /// attempts to allocate beyond pre-allocated memory will fail.
+ SafeSectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
+ SafeSectionMemoryManager(const SafeSectionMemoryManager &) = delete;
+ void operator=(const SafeSectionMemoryManager &) = delete;
+ ~SafeSectionMemoryManager() override;
+
+ /// Enable reserveAllocationSpace when requested.
+ bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
+
+ /// Implements allocating all memory in a single block. This is required to
+ /// limit memory offsets to fit the ARM ABI; large memory systems may
+ /// otherwise allocate separate sections too far apart.
+#if LLVM_VERSION_MAJOR < 16
+ virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign) override;
+#else
+ void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
+ uintptr_t RODataSize, Align RODataAlign,
+ uintptr_t RWDataSize, Align RWDataAlign) override;
+#endif
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) override;
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID, StringRef SectionName,
+ bool isReadOnly) override;
+
+ /// Update section-specific memory permissions and other attributes.
+ ///
+ /// This method is called when object loading is complete and section page
+ /// permissions can be applied. It is up to the memory manager implementation
+ /// to decide whether or not to act on this method. The memory manager will
+ /// typically allocate all sections as read-write and then apply specific
+ /// permissions when this method is called. Code sections cannot be executed
+ /// until this function has been called. In addition, any cache coherency
+ /// operations needed to reliably use the memory are also performed.
+ ///
+ /// \returns true if an error occurred, false otherwise.
+ bool finalizeMemory(std::string *ErrMsg = nullptr) override;
+
+ /// Invalidate instruction cache for code sections.
+ ///
+ /// Some platforms with separate data cache and instruction cache require
+ /// explicit cache flush, otherwise JIT code manipulations (like resolved
+ /// relocations) will get to the data cache but not to the instruction cache.
+ ///
+ /// This method is called from finalizeMemory.
+ virtual void invalidateInstructionCache();
+
+private:
+ struct FreeMemBlock {
+ // The actual block of free memory
+ sys::MemoryBlock Free;
+ // If there is a pending allocation from the same reservation right before
+ // this block, store it's index in PendingMem, to be able to update the
+ // pending region if part of this block is allocated, rather than having to
+ // create a new one
+ unsigned PendingPrefixIndex;
+ };
+
+ struct MemoryGroup {
+ // PendingMem contains all blocks of memory (subblocks of AllocatedMem)
+ // which have not yet had their permissions applied, but have been given
+ // out to the user. FreeMem contains all block of memory, which have
+ // neither had their permissions applied, nor been given out to the user.
+ SmallVector<sys::MemoryBlock, 16> PendingMem;
+ SmallVector<FreeMemBlock, 16> FreeMem;
+
+ // All memory blocks that have been requested from the system
+ SmallVector<sys::MemoryBlock, 16> AllocatedMem;
+
+ sys::MemoryBlock Near;
+ };
+
+ uint8_t *allocateSection(AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment);
+
+ std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions);
+
+ bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
+
+ void anchor() override;
+
+ MemoryGroup CodeMem;
+ MemoryGroup RWDataMem;
+ MemoryGroup RODataMem;
+ MemoryMapper *MMapper;
+ std::unique_ptr<MemoryMapper> OwnedMMapper;
+ bool ReserveAllocation;
+};
+
+} // end namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_SAFESECTIONMEMORYMANAGER_H
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..99c6fd15849 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -18,6 +18,7 @@
#ifdef USE_LLVM
#include <llvm-c/Types.h>
+#include <llvm-c/OrcEE.h>
/*
@@ -135,6 +136,7 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef RS);
#ifdef __cplusplus
} /* extern "C" */
--
2.39.3 (Apple Git-146)
On Tue, Aug 27, 2024 at 11:32 AM Thomas Munro <thomas.munro@gmail.com> wrote:
SectorMemoryManager
Erm, "Section". (I was working on some file system stuff at the
weekend, and apparently my fingers now auto-complete "sector".)
On Tue, Aug 27, 2024 at 1:33 AM Thomas Munro <thomas.munro@gmail.com> wrote:
I am sure this requires changes for various LLVM versions. I tested
it with LLVM 14 on a Mac where I've never managed to reproduce the
original complaint, but ... ooooh, this might be exacerbated by ASLR,
and macOS only has a small ALSR slide window (16M or 256M apparently,
according to me in another thread), so I'd probably have to interpose
my own mmap() to choose some more interesting addresses, or run some
other OS, but that's quite enough rabbit holes for one morning.
I've tested the patch. I had to make sure the issue was triggered on
master first. The issue didn't happen with 4GB shared_buffers and 64
partitions. However, increasing to 6GB and 128 partitions triggered
the issue.
The architecture check in the patch was incorrect (__arch64__ instead
of __aarch64__, glad to see I'm not the only one being confused with
aarch64 and arm64 :)) but once fixed, it worked and avoided the
segfault.
I've run some additional tests to try to test different parameters:
- I've tried disabling randomize_va_space, the issue still happened
even with ASLR disabled.
- I've tested different PG versions. With 14 and 15, 4GB and 64
partitions were enough. Starting PG 16, I had to increase
shared_buffers to 6GB and partitions to 128. I've been able to trigger
the issue on all versions from 14 to master (which was expected but I
wanted confirmation)
- I haven't been able to reproduce this on a macOS either. I've tried
to remove MemGroup.Near hint so mmap addresses would be more random
and played with different shared_buffers and partition values without
success
I've modified the patch with 3 changes:
- meson.build was using SectionMemoryManager.cpp file name, I've
replaced with SafeSectionMemoryManager.cpp
- Use __aarch64__ instead of __arch64__
- Moved the architecture switch to llvm_create_object_layer and go
through the normal
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager on non
arm64 architectures. There's no need to use the custom memory manager
for non arm64 so it looked better to avoid it entirely if there's no
need for the reserve allocation.
Attachments:
v3-0001-XXX-LLVM-ARM-relocation-bug-mitigation.patchapplication/octet-stream; name=v3-0001-XXX-LLVM-ARM-relocation-bug-mitigation.patchDownload
From 02b4d45c48e12a4fcd622f6703a3d422b2186cc0 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: XXX LLVM ARM relocation bug mitigation.
The patched code from https://github.com/llvm/llvm-project/pull/71968,
moved into a new class SafeSectionMemoryManager, adjusted to work on LLVM < 16,
and used in place of the regular memory manager.
XXX experimental
XXX this may be a terrible idea
XXX several details include #include directives would need adjustment
for prehistoric LLVM versions
Reported-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Discussion: https://postgr.es/m/CAO6_Xqr63qj%3DSx7HY6ZiiQ6R_JbX%2B-p6sTPwDYwTWZjUmjsYBg%40mail.gmail.com
Signed-off-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
---
src/backend/jit/llvm/Makefile | 3 +-
.../jit/llvm/SafeSectionMemoryManager.cpp | 386 ++++++++++++++++++
src/backend/jit/llvm/llvmjit.c | 6 +
src/backend/jit/llvm/llvmjit_wrap.cpp | 15 +
src/backend/jit/llvm/meson.build | 1 +
src/include/jit/SafeSectionMemoryManager.h | 225 ++++++++++
src/include/jit/llvmjit.h | 2 +
7 files changed, 637 insertions(+), 1 deletion(-)
create mode 100644 src/backend/jit/llvm/SafeSectionMemoryManager.cpp
create mode 100644 src/include/jit/SafeSectionMemoryManager.h
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
index bfe5c207a2b..d7aeaff4472 100644
--- a/src/backend/jit/llvm/Makefile
+++ b/src/backend/jit/llvm/Makefile
@@ -47,7 +47,8 @@ OBJS += \
llvmjit.o \
llvmjit_error.o \
llvmjit_inline.o \
- llvmjit_wrap.o
+ llvmjit_wrap.o \
+ SafeSectionMemoryManager.o
# Code generation
OBJS += \
diff --git a/src/backend/jit/llvm/SafeSectionMemoryManager.cpp b/src/backend/jit/llvm/SafeSectionMemoryManager.cpp
new file mode 100644
index 00000000000..88d7f43f621
--- /dev/null
+++ b/src/backend/jit/llvm/SafeSectionMemoryManager.cpp
@@ -0,0 +1,386 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to SafeSectionMemoryManager, so we can support the ARM
+ * memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the section-based memory manager used by the MCJIT
+// execution engine and RuntimeDyld
+//
+//===----------------------------------------------------------------------===//
+
+#include "jit/SafeSectionMemoryManager.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Process.h"
+
+namespace llvm {
+
+bool SafeSectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
+ uintptr_t Size) const {
+ for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= Size)
+ return true;
+ }
+ return false;
+}
+
+#if LLVM_VERSION_MAJOR < 16
+void SafeSectionMemoryManager::reserveAllocationSpace(uintptr_t CodeSize,
+ uint32_t CodeAlign_i,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign_i,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign_i) {
+ Align CodeAlign(CodeAlign_i);
+ Align RODataAlign(RODataAlign_i);
+ Align RWDataAlign(RWDataAlign_i);
+#else
+void SafeSectionMemoryManager::reserveAllocationSpace(
+ uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
+ Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
+#endif
+ if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
+ return;
+
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ // Code alignment needs to be at least the stub alignment - however, we
+ // don't have an easy way to get that here so as a workaround, we assume
+ // it's 8, which is the largest value I observed across all platforms.
+ constexpr uint64_t StubAlign = 8;
+ CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
+ RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
+ RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
+
+ // Get space required for each section. Use the same calculation as
+ // allocateSection because we need to be able to satisfy it.
+ uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
+ uint64_t RequiredRODataSize =
+ alignTo(RODataSize, RODataAlign) + RODataAlign.value();
+ uint64_t RequiredRWDataSize =
+ alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
+
+ if (hasSpace(CodeMem, RequiredCodeSize) &&
+ hasSpace(RODataMem, RequiredRODataSize) &&
+ hasSpace(RWDataMem, RequiredRWDataSize)) {
+ // Sufficient space in contiguous block already available.
+ return;
+ }
+
+ // MemoryManager does not have functions for releasing memory after it's
+ // allocated. Normally it tries to use any excess blocks that were allocated
+ // due to page alignment, but if we have insufficient free memory for the
+ // request this can lead to allocating disparate memory that can violate the
+ // ARM ABI. Clear free memory so only the new allocations are used, but do
+ // not release allocated memory as it may still be in-use.
+ CodeMem.FreeMem.clear();
+ RODataMem.FreeMem.clear();
+ RWDataMem.FreeMem.clear();
+
+ // Round up to the nearest page size. Blocks must be page-aligned.
+ RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
+ RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
+ RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
+ uint64_t RequiredSize =
+ RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
+
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ AllocationPurpose::RWData, RequiredSize, nullptr,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ return;
+ }
+ // CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
+ CodeMem.AllocatedMem.push_back(MB);
+ uintptr_t Addr = (uintptr_t)MB.base();
+ FreeMemBlock FreeMB;
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+
+ if (CodeSize > 0) {
+ assert(isAddrAligned(CodeAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
+ CodeMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredCodeSize;
+ }
+
+ if (RODataSize > 0) {
+ assert(isAddrAligned(RODataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
+ RODataMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredRODataSize;
+ }
+
+ if (RWDataSize > 0) {
+ assert(isAddrAligned(RWDataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
+ RWDataMem.FreeMem.push_back(FreeMB);
+ }
+}
+
+uint8_t *SafeSectionMemoryManager::allocateDataSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName,
+ bool IsReadOnly) {
+ if (IsReadOnly)
+ return allocateSection(SafeSectionMemoryManager::AllocationPurpose::ROData,
+ Size, Alignment);
+ return allocateSection(SafeSectionMemoryManager::AllocationPurpose::RWData, Size,
+ Alignment);
+}
+
+uint8_t *SafeSectionMemoryManager::allocateCodeSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) {
+ return allocateSection(SafeSectionMemoryManager::AllocationPurpose::Code, Size,
+ Alignment);
+}
+
+uint8_t *SafeSectionMemoryManager::allocateSection(
+ SafeSectionMemoryManager::AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment) {
+ if (!Alignment)
+ Alignment = 16;
+
+ assert(!(Alignment & (Alignment - 1)) && "Alignment must be a power of two.");
+
+ uintptr_t RequiredSize = Alignment * ((Size + Alignment - 1) / Alignment + 1);
+ uintptr_t Addr = 0;
+
+ MemoryGroup &MemGroup = [&]() -> MemoryGroup & {
+ switch (Purpose) {
+ case AllocationPurpose::Code:
+ return CodeMem;
+ case AllocationPurpose::ROData:
+ return RODataMem;
+ case AllocationPurpose::RWData:
+ return RWDataMem;
+ }
+ llvm_unreachable("Unknown SafeSectionMemoryManager::AllocationPurpose");
+ }();
+
+ // Look in the list of free memory regions and use a block there if one
+ // is available.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= RequiredSize) {
+ Addr = (uintptr_t)FreeMB.Free.base();
+ uintptr_t EndOfBlock = Addr + FreeMB.Free.allocatedSize();
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ if (FreeMB.PendingPrefixIndex == (unsigned)-1) {
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // Remember this pending block, such that future allocations can just
+ // modify it rather than creating a new one
+ FreeMB.PendingPrefixIndex = MemGroup.PendingMem.size() - 1;
+ } else {
+ sys::MemoryBlock &PendingMB =
+ MemGroup.PendingMem[FreeMB.PendingPrefixIndex];
+ PendingMB = sys::MemoryBlock(PendingMB.base(),
+ Addr + Size - (uintptr_t)PendingMB.base());
+ }
+
+ // Remember how much free space is now left in this block
+ FreeMB.Free =
+ sys::MemoryBlock((void *)(Addr + Size), EndOfBlock - Addr - Size);
+ return (uint8_t *)Addr;
+ }
+ }
+
+ // No pre-allocated free block was large enough. Allocate a new memory region.
+ // Note that all sections get allocated as read-write. The permissions will
+ // be updated later based on memory group.
+ //
+ // FIXME: It would be useful to define a default allocation size (or add
+ // it as a constructor parameter) to minimize the number of allocations.
+ //
+ // FIXME: Initialize the Near member for each memory group to avoid
+ // interleaving.
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ Purpose, RequiredSize, &MemGroup.Near,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ // FIXME: Add error propagation to the interface.
+ return nullptr;
+ }
+
+ // Save this address as the basis for our next request
+ MemGroup.Near = MB;
+
+ // Copy the address to all the other groups, if they have not
+ // been initialized.
+ if (CodeMem.Near.base() == nullptr)
+ CodeMem.Near = MB;
+ if (RODataMem.Near.base() == nullptr)
+ RODataMem.Near = MB;
+ if (RWDataMem.Near.base() == nullptr)
+ RWDataMem.Near = MB;
+
+ // Remember that we allocated this memory
+ MemGroup.AllocatedMem.push_back(MB);
+ Addr = (uintptr_t)MB.base();
+ uintptr_t EndOfBlock = Addr + MB.allocatedSize();
+
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // The allocateMappedMemory may allocate much more memory than we need. In
+ // this case, we store the unused memory as a free memory block.
+ unsigned FreeSize = EndOfBlock - Addr - Size;
+ if (FreeSize > 16) {
+ FreeMemBlock FreeMB;
+ FreeMB.Free = sys::MemoryBlock((void *)(Addr + Size), FreeSize);
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ MemGroup.FreeMem.push_back(FreeMB);
+ }
+
+ // Return aligned address
+ return (uint8_t *)Addr;
+}
+
+bool SafeSectionMemoryManager::finalizeMemory(std::string *ErrMsg) {
+ // FIXME: Should in-progress permissions be reverted if an error occurs?
+ std::error_code ec;
+
+ // Make code memory executable.
+ ec = applyMemoryGroupPermissions(CodeMem,
+ sys::Memory::MF_READ | sys::Memory::MF_EXEC);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Make read-only data memory read-only.
+ ec = applyMemoryGroupPermissions(RODataMem, sys::Memory::MF_READ);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Read-write data memory already has the correct permissions
+
+ // Some platforms with separate data cache and instruction cache require
+ // explicit cache flush, otherwise JIT code manipulations (like resolved
+ // relocations) will get to the data cache but not to the instruction cache.
+ invalidateInstructionCache();
+
+ return false;
+}
+
+static sys::MemoryBlock trimBlockToPageSize(sys::MemoryBlock M) {
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ size_t StartOverlap =
+ (PageSize - ((uintptr_t)M.base() % PageSize)) % PageSize;
+
+ size_t TrimmedSize = M.allocatedSize();
+ TrimmedSize -= StartOverlap;
+ TrimmedSize -= TrimmedSize % PageSize;
+
+ sys::MemoryBlock Trimmed((void *)((uintptr_t)M.base() + StartOverlap),
+ TrimmedSize);
+
+ assert(((uintptr_t)Trimmed.base() % PageSize) == 0);
+ assert((Trimmed.allocatedSize() % PageSize) == 0);
+ assert(M.base() <= Trimmed.base() &&
+ Trimmed.allocatedSize() <= M.allocatedSize());
+
+ return Trimmed;
+}
+
+std::error_code
+SafeSectionMemoryManager::applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions) {
+ for (sys::MemoryBlock &MB : MemGroup.PendingMem)
+ if (std::error_code EC = MMapper->protectMappedMemory(MB, Permissions))
+ return EC;
+
+ MemGroup.PendingMem.clear();
+
+ // Now go through free blocks and trim any of them that don't span the entire
+ // page because one of the pending blocks may have overlapped it.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ FreeMB.Free = trimBlockToPageSize(FreeMB.Free);
+ // We cleared the PendingMem list, so all these pointers are now invalid
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ }
+
+ // Remove all blocks which are now empty
+ erase_if(MemGroup.FreeMem, [](FreeMemBlock &FreeMB) {
+ return FreeMB.Free.allocatedSize() == 0;
+ });
+
+ return std::error_code();
+}
+
+void SafeSectionMemoryManager::invalidateInstructionCache() {
+ for (sys::MemoryBlock &Block : CodeMem.PendingMem)
+ sys::Memory::InvalidateInstructionCache(Block.base(),
+ Block.allocatedSize());
+}
+
+SafeSectionMemoryManager::~SafeSectionMemoryManager() {
+ for (MemoryGroup *Group : {&CodeMem, &RWDataMem, &RODataMem}) {
+ for (sys::MemoryBlock &Block : Group->AllocatedMem)
+ MMapper->releaseMappedMemory(Block);
+ }
+}
+
+SafeSectionMemoryManager::MemoryMapper::~MemoryMapper() = default;
+
+void SafeSectionMemoryManager::anchor() {}
+
+namespace {
+// Trivial implementation of SectionMemoryManager::MemoryMapper that just calls
+// into sys::Memory.
+class DefaultMMapper final : public SafeSectionMemoryManager::MemoryMapper {
+public:
+ sys::MemoryBlock
+ allocateMappedMemory(SafeSectionMemoryManager::AllocationPurpose Purpose,
+ size_t NumBytes, const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) override {
+ return sys::Memory::allocateMappedMemory(NumBytes, NearBlock, Flags, EC);
+ }
+
+ std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) override {
+ return sys::Memory::protectMappedMemory(Block, Flags);
+ }
+
+ std::error_code releaseMappedMemory(sys::MemoryBlock &M) override {
+ return sys::Memory::releaseMappedMemory(M);
+ }
+};
+} // namespace
+
+SafeSectionMemoryManager::SafeSectionMemoryManager(MemoryMapper *UnownedMM,
+ bool ReserveAlloc)
+ : MMapper(UnownedMM), OwnedMMapper(nullptr),
+ ReserveAllocation(ReserveAlloc) {
+ if (!MMapper) {
+ OwnedMMapper = std::make_unique<DefaultMMapper>();
+ MMapper = OwnedMMapper.get();
+ }
+}
+
+} // namespace llvm
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 0f6cec52496..a4ea47bcfb6 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -1278,8 +1278,14 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error)
static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
+#if defined(__aarch64__)
+ LLVMOrcObjectLayerRef objlayer =
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+#else
LLVMOrcObjectLayerRef objlayer =
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+#endif
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 641c8841ca3..6c722c431a7 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -17,13 +17,18 @@ extern "C"
}
#include <llvm-c/Core.h>
+#include <llvm-c/OrcEE.h>
/* Avoid macro clash with LLVM's C++ headers */
#undef Min
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#include <llvm/IR/Function.h>
+#include <llvm/Support/CBindingWrapping.h>
#include "jit/llvmjit.h"
+#include "jit/SafeSectionMemoryManager.h"
/*
@@ -41,3 +46,13 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<llvm::SafeSectionMemoryManager>(nullptr, true); }));
+}
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index 4a4232661ba..87e0feba32b 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -14,6 +14,7 @@ llvmjit_sources += files(
'llvmjit_error.cpp',
'llvmjit_inline.cpp',
'llvmjit_wrap.cpp',
+ 'SafeSectionMemoryManager.cpp',
)
# Code generation
diff --git a/src/include/jit/SafeSectionMemoryManager.h b/src/include/jit/SafeSectionMemoryManager.h
new file mode 100644
index 00000000000..985e85440aa
--- /dev/null
+++ b/src/include/jit/SafeSectionMemoryManager.h
@@ -0,0 +1,225 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to SafeSectionMemoryManager, so we can support the ARM
+ * memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.h - Memory manager for MCJIT/RtDyld -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of a section-based memory manager used by
+// the MCJIT execution engine and RuntimeDyld.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_SAFESECTIONMEMORYMANAGER_H
+#define LLVM_EXECUTIONENGINE_SAFESECTIONMEMORYMANAGER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Memory.h"
+#include <cstdint>
+#include <string>
+#include <system_error>
+
+namespace llvm {
+
+/// This is a simple memory manager which implements the methods called by
+/// the RuntimeDyld class to allocate memory for section-based loading of
+/// objects, usually those generated by the MCJIT execution engine.
+///
+/// This memory manager allocates all section memory as read-write. The
+/// RuntimeDyld will copy JITed section memory into these allocated blocks
+/// and perform any necessary linking and relocations.
+///
+/// Any client using this memory manager MUST ensure that section-specific
+/// page permissions have been applied before attempting to execute functions
+/// in the JITed object. Permissions can be applied either by calling
+/// MCJIT::finalizeObject or by calling SectionMemoryManager::finalizeMemory
+/// directly. Clients of MCJIT should call MCJIT::finalizeObject.
+class SafeSectionMemoryManager : public RTDyldMemoryManager {
+public:
+ /// This enum describes the various reasons to allocate pages from
+ /// allocateMappedMemory.
+ enum class AllocationPurpose {
+ Code,
+ ROData,
+ RWData,
+ };
+
+ /// Implementations of this interface are used by SectionMemoryManager to
+ /// request pages from the operating system.
+ class MemoryMapper {
+ public:
+ /// This method attempts to allocate \p NumBytes bytes of virtual memory for
+ /// \p Purpose. \p NearBlock may point to an existing allocation, in which
+ /// case an attempt is made to allocate more memory near the existing block.
+ /// The actual allocated address is not guaranteed to be near the requested
+ /// address. \p Flags is used to set the initial protection flags for the
+ /// block of the memory. \p EC [out] returns an object describing any error
+ /// that occurs.
+ ///
+ /// This method may allocate more than the number of bytes requested. The
+ /// actual number of bytes allocated is indicated in the returned
+ /// MemoryBlock.
+ ///
+ /// The start of the allocated block must be aligned with the system
+ /// allocation granularity (64K on Windows, page size on Linux). If the
+ /// address following \p NearBlock is not so aligned, it will be rounded up
+ /// to the next allocation granularity boundary.
+ ///
+ /// \r a non-null MemoryBlock if the function was successful, otherwise a
+ /// null MemoryBlock with \p EC describing the error.
+ virtual sys::MemoryBlock
+ allocateMappedMemory(AllocationPurpose Purpose, size_t NumBytes,
+ const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) = 0;
+
+ /// This method sets the protection flags for a block of memory to the state
+ /// specified by \p Flags. The behavior is not specified if the memory was
+ /// not allocated using the allocateMappedMemory method.
+ /// \p Block describes the memory block to be protected.
+ /// \p Flags specifies the new protection state to be assigned to the block.
+ ///
+ /// If \p Flags is MF_WRITE, the actual behavior varies with the operating
+ /// system (i.e. MF_READ | MF_WRITE on Windows) and the target architecture
+ /// (i.e. MF_WRITE -> MF_READ | MF_WRITE on i386).
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) = 0;
+
+ /// This method releases a block of memory that was allocated with the
+ /// allocateMappedMemory method. It should not be used to release any memory
+ /// block allocated any other way.
+ /// \p Block describes the memory to be released.
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code releaseMappedMemory(sys::MemoryBlock &M) = 0;
+
+ virtual ~MemoryMapper();
+ };
+
+ /// Creates a SectionMemoryManager instance with \p MM as the associated
+ /// memory mapper. If \p MM is nullptr then a default memory mapper is used
+ /// that directly calls into the operating system.
+ ///
+ /// If \p ReserveAlloc is true all memory will be pre-allocated, and any
+ /// attempts to allocate beyond pre-allocated memory will fail.
+ SafeSectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
+ SafeSectionMemoryManager(const SafeSectionMemoryManager &) = delete;
+ void operator=(const SafeSectionMemoryManager &) = delete;
+ ~SafeSectionMemoryManager() override;
+
+ /// Enable reserveAllocationSpace when requested.
+ bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
+
+ /// Implements allocating all memory in a single block. This is required to
+ /// limit memory offsets to fit the ARM ABI; large memory systems may
+ /// otherwise allocate separate sections too far apart.
+#if LLVM_VERSION_MAJOR < 16
+ virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign) override;
+#else
+ void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
+ uintptr_t RODataSize, Align RODataAlign,
+ uintptr_t RWDataSize, Align RWDataAlign) override;
+#endif
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) override;
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID, StringRef SectionName,
+ bool isReadOnly) override;
+
+ /// Update section-specific memory permissions and other attributes.
+ ///
+ /// This method is called when object loading is complete and section page
+ /// permissions can be applied. It is up to the memory manager implementation
+ /// to decide whether or not to act on this method. The memory manager will
+ /// typically allocate all sections as read-write and then apply specific
+ /// permissions when this method is called. Code sections cannot be executed
+ /// until this function has been called. In addition, any cache coherency
+ /// operations needed to reliably use the memory are also performed.
+ ///
+ /// \returns true if an error occurred, false otherwise.
+ bool finalizeMemory(std::string *ErrMsg = nullptr) override;
+
+ /// Invalidate instruction cache for code sections.
+ ///
+ /// Some platforms with separate data cache and instruction cache require
+ /// explicit cache flush, otherwise JIT code manipulations (like resolved
+ /// relocations) will get to the data cache but not to the instruction cache.
+ ///
+ /// This method is called from finalizeMemory.
+ virtual void invalidateInstructionCache();
+
+private:
+ struct FreeMemBlock {
+ // The actual block of free memory
+ sys::MemoryBlock Free;
+ // If there is a pending allocation from the same reservation right before
+ // this block, store it's index in PendingMem, to be able to update the
+ // pending region if part of this block is allocated, rather than having to
+ // create a new one
+ unsigned PendingPrefixIndex;
+ };
+
+ struct MemoryGroup {
+ // PendingMem contains all blocks of memory (subblocks of AllocatedMem)
+ // which have not yet had their permissions applied, but have been given
+ // out to the user. FreeMem contains all block of memory, which have
+ // neither had their permissions applied, nor been given out to the user.
+ SmallVector<sys::MemoryBlock, 16> PendingMem;
+ SmallVector<FreeMemBlock, 16> FreeMem;
+
+ // All memory blocks that have been requested from the system
+ SmallVector<sys::MemoryBlock, 16> AllocatedMem;
+
+ sys::MemoryBlock Near;
+ };
+
+ uint8_t *allocateSection(AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment);
+
+ std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions);
+
+ bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
+
+ void anchor() override;
+
+ MemoryGroup CodeMem;
+ MemoryGroup RWDataMem;
+ MemoryGroup RODataMem;
+ MemoryMapper *MMapper;
+ std::unique_ptr<MemoryMapper> OwnedMMapper;
+ bool ReserveAllocation;
+};
+
+} // end namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_SAFESECTIONMEMORYMANAGER_H
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..5c600e35604 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -18,6 +18,7 @@
#ifdef USE_LLVM
#include <llvm-c/Types.h>
+#include <llvm-c/OrcEE.h>
/*
@@ -135,6 +136,7 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES);
#ifdef __cplusplus
} /* extern "C" */
--
2.39.3 (Apple Git-146)
Thanks! And that's great news. Do you want to report this experience
to the PR, in support of committing it? That'd make it seem easier to
consider shipping a back-ported copy...
On Tue, Aug 27, 2024 at 12:01 PM Thomas Munro <thomas.munro@gmail.com> wrote:
Thanks! And that's great news. Do you want to report this experience
to the PR, in support of committing it? That'd make it seem easier to
consider shipping a back-ported copy...
Yes, I will do that.
On Wed, Aug 28, 2024 at 12:07 AM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:
On Tue, Aug 27, 2024 at 12:01 PM Thomas Munro <thomas.munro@gmail.com> wrote:
Thanks! And that's great news. Do you want to report this experience
to the PR, in support of committing it? That'd make it seem easier to
consider shipping a back-ported copy...Yes, I will do that.
Thanks. Here's a slightly tidied up version:
1. I used namespace llvm::backport, instead of a different class
name. That minimises the diff against their code.
2. I tested against LLVM 10-18, and found that 10 and 11 lack some
needed symbols. So I just hid this code from them. Even though our
stable branches support those and even older versions, I am not sure
if it's worth trying to do something about that for EOL'd distros that
no one has ever complained about. I am willing to try harder if
someone thinks that's important...
One little problem I am aware of is that if you make an empty .o,
macOS's new linker issues a warning, but I think I could live with
that. I guess I could put a dummy symbol in there... FWIW those old
LLVM versions spit out tons of other warnings from the headers on
newer compilers too, so *shrug*, don't use them? But then if this
code lands in LLVM 19 we'll also be hiding it for 19+ too.
Next, I think we should wait to see if the LLVM project commits that
PR, this so that we can sync with their 19.x stable branch, instead of
using code from a PR. Our next minor release is in November, so we
have some time. If they don't commit it, we can consider it anyway: I
mean, it's crashing all over the place in production, and we see that
other projects are shipping this code already.
Attachments:
v4-0001-Backport-of-LLVM-code-to-fix-ARM-relocation-bug.patchapplication/octet-stream; name=v4-0001-Backport-of-LLVM-code-to-fix-ARM-relocation-bug.patchDownload
From 85f28f6238fa838d8a9eb85becf0f49b55f8bad0 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: [PATCH v4] Backport of LLVM code to fix ARM relocation bug.
Supply a new memory manager for RuntimeDyld that avoids putting ARM code
too far apart. This is the code from
https://github.com/llvm/llvm-project/pull/71968, copied into our tree
and moved into a new namespace llvm::backport, with minor adjustments to
work on LLVM 12-18.
This should fix the spate of crashes we've been receiving lately from
users on ARM systems.
XXX Ideally the LLVM project will commit this, and then we can resync
with the code in the LLVM 19.x stable branch, instead of using the code
from their PR, before we ship it!
Reported-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Reviewed-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Discussion: https://postgr.es/m/CAO6_Xqr63qj%3DSx7HY6ZiiQ6R_JbX%2B-p6sTPwDYwTWZjUmjsYBg%40mail.gmail.com
---
src/backend/jit/llvm/Makefile | 3 +-
src/backend/jit/llvm/SectionMemoryManager.cpp | 392 ++++++++++++++++++
src/backend/jit/llvm/llvmjit.c | 6 +
src/backend/jit/llvm/llvmjit_wrap.cpp | 22 +
src/backend/jit/llvm/meson.build | 1 +
src/include/jit/SectionMemoryManager.h | 231 +++++++++++
src/include/jit/llvmjit.h | 6 +
7 files changed, 660 insertions(+), 1 deletion(-)
create mode 100644 src/backend/jit/llvm/SectionMemoryManager.cpp
create mode 100644 src/include/jit/SectionMemoryManager.h
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
index bfe5c207a2b..e8c12060b93 100644
--- a/src/backend/jit/llvm/Makefile
+++ b/src/backend/jit/llvm/Makefile
@@ -47,7 +47,8 @@ OBJS += \
llvmjit.o \
llvmjit_error.o \
llvmjit_inline.o \
- llvmjit_wrap.o
+ llvmjit_wrap.o \
+ SectionMemoryManager.o
# Code generation
OBJS += \
diff --git a/src/backend/jit/llvm/SectionMemoryManager.cpp b/src/backend/jit/llvm/SectionMemoryManager.cpp
new file mode 100644
index 00000000000..16fe7a89c24
--- /dev/null
+++ b/src/backend/jit/llvm/SectionMemoryManager.cpp
@@ -0,0 +1,392 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the section-based memory manager used by the MCJIT
+// execution engine and RuntimeDyld
+//
+//===----------------------------------------------------------------------===//
+
+#include "jit/SectionMemoryManager.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Process.h"
+
+#if LLVM_VERSION_MAJOR > 11
+
+namespace llvm {
+namespace backport {
+
+bool SectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
+ uintptr_t Size) const {
+ for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= Size)
+ return true;
+ }
+ return false;
+}
+
+#if LLVM_VERSION_MAJOR < 16
+void SectionMemoryManager::reserveAllocationSpace(uintptr_t CodeSize,
+ uint32_t CodeAlign_i,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign_i,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign_i) {
+ Align CodeAlign(CodeAlign_i);
+ Align RODataAlign(RODataAlign_i);
+ Align RWDataAlign(RWDataAlign_i);
+#else
+void SectionMemoryManager::reserveAllocationSpace(
+ uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
+ Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
+#endif
+ if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
+ return;
+
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ // Code alignment needs to be at least the stub alignment - however, we
+ // don't have an easy way to get that here so as a workaround, we assume
+ // it's 8, which is the largest value I observed across all platforms.
+ constexpr uint64_t StubAlign = 8;
+ CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
+ RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
+ RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
+
+ // Get space required for each section. Use the same calculation as
+ // allocateSection because we need to be able to satisfy it.
+ uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
+ uint64_t RequiredRODataSize =
+ alignTo(RODataSize, RODataAlign) + RODataAlign.value();
+ uint64_t RequiredRWDataSize =
+ alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
+
+ if (hasSpace(CodeMem, RequiredCodeSize) &&
+ hasSpace(RODataMem, RequiredRODataSize) &&
+ hasSpace(RWDataMem, RequiredRWDataSize)) {
+ // Sufficient space in contiguous block already available.
+ return;
+ }
+
+ // MemoryManager does not have functions for releasing memory after it's
+ // allocated. Normally it tries to use any excess blocks that were allocated
+ // due to page alignment, but if we have insufficient free memory for the
+ // request this can lead to allocating disparate memory that can violate the
+ // ARM ABI. Clear free memory so only the new allocations are used, but do
+ // not release allocated memory as it may still be in-use.
+ CodeMem.FreeMem.clear();
+ RODataMem.FreeMem.clear();
+ RWDataMem.FreeMem.clear();
+
+ // Round up to the nearest page size. Blocks must be page-aligned.
+ RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
+ RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
+ RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
+ uint64_t RequiredSize =
+ RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
+
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ AllocationPurpose::RWData, RequiredSize, nullptr,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ return;
+ }
+ // CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
+ CodeMem.AllocatedMem.push_back(MB);
+ uintptr_t Addr = (uintptr_t)MB.base();
+ FreeMemBlock FreeMB;
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+
+ if (CodeSize > 0) {
+ assert(isAddrAligned(CodeAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
+ CodeMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredCodeSize;
+ }
+
+ if (RODataSize > 0) {
+ assert(isAddrAligned(RODataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
+ RODataMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredRODataSize;
+ }
+
+ if (RWDataSize > 0) {
+ assert(isAddrAligned(RWDataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
+ RWDataMem.FreeMem.push_back(FreeMB);
+ }
+}
+
+uint8_t *SectionMemoryManager::allocateDataSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName,
+ bool IsReadOnly) {
+ if (IsReadOnly)
+ return allocateSection(SectionMemoryManager::AllocationPurpose::ROData,
+ Size, Alignment);
+ return allocateSection(SectionMemoryManager::AllocationPurpose::RWData, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateCodeSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) {
+ return allocateSection(SectionMemoryManager::AllocationPurpose::Code, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateSection(
+ SectionMemoryManager::AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment) {
+ if (!Alignment)
+ Alignment = 16;
+
+ assert(!(Alignment & (Alignment - 1)) && "Alignment must be a power of two.");
+
+ uintptr_t RequiredSize = Alignment * ((Size + Alignment - 1) / Alignment + 1);
+ uintptr_t Addr = 0;
+
+ MemoryGroup &MemGroup = [&]() -> MemoryGroup & {
+ switch (Purpose) {
+ case AllocationPurpose::Code:
+ return CodeMem;
+ case AllocationPurpose::ROData:
+ return RODataMem;
+ case AllocationPurpose::RWData:
+ return RWDataMem;
+ }
+ llvm_unreachable("Unknown SectionMemoryManager::AllocationPurpose");
+ }();
+
+ // Look in the list of free memory regions and use a block there if one
+ // is available.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= RequiredSize) {
+ Addr = (uintptr_t)FreeMB.Free.base();
+ uintptr_t EndOfBlock = Addr + FreeMB.Free.allocatedSize();
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ if (FreeMB.PendingPrefixIndex == (unsigned)-1) {
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // Remember this pending block, such that future allocations can just
+ // modify it rather than creating a new one
+ FreeMB.PendingPrefixIndex = MemGroup.PendingMem.size() - 1;
+ } else {
+ sys::MemoryBlock &PendingMB =
+ MemGroup.PendingMem[FreeMB.PendingPrefixIndex];
+ PendingMB = sys::MemoryBlock(PendingMB.base(),
+ Addr + Size - (uintptr_t)PendingMB.base());
+ }
+
+ // Remember how much free space is now left in this block
+ FreeMB.Free =
+ sys::MemoryBlock((void *)(Addr + Size), EndOfBlock - Addr - Size);
+ return (uint8_t *)Addr;
+ }
+ }
+
+ // No pre-allocated free block was large enough. Allocate a new memory region.
+ // Note that all sections get allocated as read-write. The permissions will
+ // be updated later based on memory group.
+ //
+ // FIXME: It would be useful to define a default allocation size (or add
+ // it as a constructor parameter) to minimize the number of allocations.
+ //
+ // FIXME: Initialize the Near member for each memory group to avoid
+ // interleaving.
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ Purpose, RequiredSize, &MemGroup.Near,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ // FIXME: Add error propagation to the interface.
+ return nullptr;
+ }
+
+ // Save this address as the basis for our next request
+ MemGroup.Near = MB;
+
+ // Copy the address to all the other groups, if they have not
+ // been initialized.
+ if (CodeMem.Near.base() == nullptr)
+ CodeMem.Near = MB;
+ if (RODataMem.Near.base() == nullptr)
+ RODataMem.Near = MB;
+ if (RWDataMem.Near.base() == nullptr)
+ RWDataMem.Near = MB;
+
+ // Remember that we allocated this memory
+ MemGroup.AllocatedMem.push_back(MB);
+ Addr = (uintptr_t)MB.base();
+ uintptr_t EndOfBlock = Addr + MB.allocatedSize();
+
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // The allocateMappedMemory may allocate much more memory than we need. In
+ // this case, we store the unused memory as a free memory block.
+ unsigned FreeSize = EndOfBlock - Addr - Size;
+ if (FreeSize > 16) {
+ FreeMemBlock FreeMB;
+ FreeMB.Free = sys::MemoryBlock((void *)(Addr + Size), FreeSize);
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ MemGroup.FreeMem.push_back(FreeMB);
+ }
+
+ // Return aligned address
+ return (uint8_t *)Addr;
+}
+
+bool SectionMemoryManager::finalizeMemory(std::string *ErrMsg) {
+ // FIXME: Should in-progress permissions be reverted if an error occurs?
+ std::error_code ec;
+
+ // Make code memory executable.
+ ec = applyMemoryGroupPermissions(CodeMem,
+ sys::Memory::MF_READ | sys::Memory::MF_EXEC);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Make read-only data memory read-only.
+ ec = applyMemoryGroupPermissions(RODataMem, sys::Memory::MF_READ);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Read-write data memory already has the correct permissions
+
+ // Some platforms with separate data cache and instruction cache require
+ // explicit cache flush, otherwise JIT code manipulations (like resolved
+ // relocations) will get to the data cache but not to the instruction cache.
+ invalidateInstructionCache();
+
+ return false;
+}
+
+static sys::MemoryBlock trimBlockToPageSize(sys::MemoryBlock M) {
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ size_t StartOverlap =
+ (PageSize - ((uintptr_t)M.base() % PageSize)) % PageSize;
+
+ size_t TrimmedSize = M.allocatedSize();
+ TrimmedSize -= StartOverlap;
+ TrimmedSize -= TrimmedSize % PageSize;
+
+ sys::MemoryBlock Trimmed((void *)((uintptr_t)M.base() + StartOverlap),
+ TrimmedSize);
+
+ assert(((uintptr_t)Trimmed.base() % PageSize) == 0);
+ assert((Trimmed.allocatedSize() % PageSize) == 0);
+ assert(M.base() <= Trimmed.base() &&
+ Trimmed.allocatedSize() <= M.allocatedSize());
+
+ return Trimmed;
+}
+
+std::error_code
+SectionMemoryManager::applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions) {
+ for (sys::MemoryBlock &MB : MemGroup.PendingMem)
+ if (std::error_code EC = MMapper->protectMappedMemory(MB, Permissions))
+ return EC;
+
+ MemGroup.PendingMem.clear();
+
+ // Now go through free blocks and trim any of them that don't span the entire
+ // page because one of the pending blocks may have overlapped it.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ FreeMB.Free = trimBlockToPageSize(FreeMB.Free);
+ // We cleared the PendingMem list, so all these pointers are now invalid
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ }
+
+ // Remove all blocks which are now empty
+ erase_if(MemGroup.FreeMem, [](FreeMemBlock &FreeMB) {
+ return FreeMB.Free.allocatedSize() == 0;
+ });
+
+ return std::error_code();
+}
+
+void SectionMemoryManager::invalidateInstructionCache() {
+ for (sys::MemoryBlock &Block : CodeMem.PendingMem)
+ sys::Memory::InvalidateInstructionCache(Block.base(),
+ Block.allocatedSize());
+}
+
+SectionMemoryManager::~SectionMemoryManager() {
+ for (MemoryGroup *Group : {&CodeMem, &RWDataMem, &RODataMem}) {
+ for (sys::MemoryBlock &Block : Group->AllocatedMem)
+ MMapper->releaseMappedMemory(Block);
+ }
+}
+
+SectionMemoryManager::MemoryMapper::~MemoryMapper() = default;
+
+void SectionMemoryManager::anchor() {}
+
+namespace {
+// Trivial implementation of SectionMemoryManager::MemoryMapper that just calls
+// into sys::Memory.
+class DefaultMMapper final : public SectionMemoryManager::MemoryMapper {
+public:
+ sys::MemoryBlock
+ allocateMappedMemory(SectionMemoryManager::AllocationPurpose Purpose,
+ size_t NumBytes, const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) override {
+ return sys::Memory::allocateMappedMemory(NumBytes, NearBlock, Flags, EC);
+ }
+
+ std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) override {
+ return sys::Memory::protectMappedMemory(Block, Flags);
+ }
+
+ std::error_code releaseMappedMemory(sys::MemoryBlock &M) override {
+ return sys::Memory::releaseMappedMemory(M);
+ }
+};
+} // namespace
+
+SectionMemoryManager::SectionMemoryManager(MemoryMapper *UnownedMM,
+ bool ReserveAlloc)
+ : MMapper(UnownedMM), OwnedMMapper(nullptr),
+ ReserveAllocation(ReserveAlloc) {
+ if (!MMapper) {
+ OwnedMMapper = std::make_unique<DefaultMMapper>();
+ MMapper = OwnedMMapper.get();
+ }
+}
+
+} // namespace backport
+} // namespace llvm
+
+#endif
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 0f6cec52496..a4ea47bcfb6 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -1278,8 +1278,14 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error)
static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
+#if defined(__aarch64__)
+ LLVMOrcObjectLayerRef objlayer =
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+#else
LLVMOrcObjectLayerRef objlayer =
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+#endif
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 641c8841ca3..27f610a8bc8 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -17,14 +17,24 @@ extern "C"
}
#include <llvm-c/Core.h>
+#if LLVM_VERSION_MAJOR > 11
+#include <llvm-c/OrcEE.h>
+#endif
/* Avoid macro clash with LLVM's C++ headers */
#undef Min
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#include <llvm/IR/Function.h>
+#include <llvm/Support/CBindingWrapping.h>
#include "jit/llvmjit.h"
+#if LLVM_VERSION_MAJOR > 11
+#include "jit/SectionMemoryManager.h"
+#endif
+
/*
* C-API extensions.
@@ -41,3 +51,15 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+#if LLVM_VERSION_MAJOR > 11
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<llvm::backport::SectionMemoryManager>(nullptr, true); }));
+}
+#endif
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index 4a4232661ba..b76e2e70547 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -14,6 +14,7 @@ llvmjit_sources += files(
'llvmjit_error.cpp',
'llvmjit_inline.cpp',
'llvmjit_wrap.cpp',
+ 'SectionMemoryManager.cpp',
)
# Code generation
diff --git a/src/include/jit/SectionMemoryManager.h b/src/include/jit/SectionMemoryManager.h
new file mode 100644
index 00000000000..bcdf7d691fe
--- /dev/null
+++ b/src/include/jit/SectionMemoryManager.h
@@ -0,0 +1,231 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.h - Memory manager for MCJIT/RtDyld -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of a section-based memory manager used by
+// the MCJIT execution engine and RuntimeDyld.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+#define LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Memory.h"
+#include <cstdint>
+#include <string>
+#include <system_error>
+
+#if LLVM_VERSION_MAJOR > 11
+
+namespace llvm {
+namespace backport {
+
+/// This is a simple memory manager which implements the methods called by
+/// the RuntimeDyld class to allocate memory for section-based loading of
+/// objects, usually those generated by the MCJIT execution engine.
+///
+/// This memory manager allocates all section memory as read-write. The
+/// RuntimeDyld will copy JITed section memory into these allocated blocks
+/// and perform any necessary linking and relocations.
+///
+/// Any client using this memory manager MUST ensure that section-specific
+/// page permissions have been applied before attempting to execute functions
+/// in the JITed object. Permissions can be applied either by calling
+/// MCJIT::finalizeObject or by calling SectionMemoryManager::finalizeMemory
+/// directly. Clients of MCJIT should call MCJIT::finalizeObject.
+class SectionMemoryManager : public RTDyldMemoryManager {
+public:
+ /// This enum describes the various reasons to allocate pages from
+ /// allocateMappedMemory.
+ enum class AllocationPurpose {
+ Code,
+ ROData,
+ RWData,
+ };
+
+ /// Implementations of this interface are used by SectionMemoryManager to
+ /// request pages from the operating system.
+ class MemoryMapper {
+ public:
+ /// This method attempts to allocate \p NumBytes bytes of virtual memory for
+ /// \p Purpose. \p NearBlock may point to an existing allocation, in which
+ /// case an attempt is made to allocate more memory near the existing block.
+ /// The actual allocated address is not guaranteed to be near the requested
+ /// address. \p Flags is used to set the initial protection flags for the
+ /// block of the memory. \p EC [out] returns an object describing any error
+ /// that occurs.
+ ///
+ /// This method may allocate more than the number of bytes requested. The
+ /// actual number of bytes allocated is indicated in the returned
+ /// MemoryBlock.
+ ///
+ /// The start of the allocated block must be aligned with the system
+ /// allocation granularity (64K on Windows, page size on Linux). If the
+ /// address following \p NearBlock is not so aligned, it will be rounded up
+ /// to the next allocation granularity boundary.
+ ///
+ /// \r a non-null MemoryBlock if the function was successful, otherwise a
+ /// null MemoryBlock with \p EC describing the error.
+ virtual sys::MemoryBlock
+ allocateMappedMemory(AllocationPurpose Purpose, size_t NumBytes,
+ const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) = 0;
+
+ /// This method sets the protection flags for a block of memory to the state
+ /// specified by \p Flags. The behavior is not specified if the memory was
+ /// not allocated using the allocateMappedMemory method.
+ /// \p Block describes the memory block to be protected.
+ /// \p Flags specifies the new protection state to be assigned to the block.
+ ///
+ /// If \p Flags is MF_WRITE, the actual behavior varies with the operating
+ /// system (i.e. MF_READ | MF_WRITE on Windows) and the target architecture
+ /// (i.e. MF_WRITE -> MF_READ | MF_WRITE on i386).
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) = 0;
+
+ /// This method releases a block of memory that was allocated with the
+ /// allocateMappedMemory method. It should not be used to release any memory
+ /// block allocated any other way.
+ /// \p Block describes the memory to be released.
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code releaseMappedMemory(sys::MemoryBlock &M) = 0;
+
+ virtual ~MemoryMapper();
+ };
+
+ /// Creates a SectionMemoryManager instance with \p MM as the associated
+ /// memory mapper. If \p MM is nullptr then a default memory mapper is used
+ /// that directly calls into the operating system.
+ ///
+ /// If \p ReserveAlloc is true all memory will be pre-allocated, and any
+ /// attempts to allocate beyond pre-allocated memory will fail.
+ SectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
+ SectionMemoryManager(const SectionMemoryManager &) = delete;
+ void operator=(const SectionMemoryManager &) = delete;
+ ~SectionMemoryManager() override;
+
+ /// Enable reserveAllocationSpace when requested.
+ bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
+
+ /// Implements allocating all memory in a single block. This is required to
+ /// limit memory offsets to fit the ARM ABI; large memory systems may
+ /// otherwise allocate separate sections too far apart.
+#if LLVM_VERSION_MAJOR < 16
+ virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign) override;
+#else
+ void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
+ uintptr_t RODataSize, Align RODataAlign,
+ uintptr_t RWDataSize, Align RWDataAlign) override;
+#endif
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) override;
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID, StringRef SectionName,
+ bool isReadOnly) override;
+
+ /// Update section-specific memory permissions and other attributes.
+ ///
+ /// This method is called when object loading is complete and section page
+ /// permissions can be applied. It is up to the memory manager implementation
+ /// to decide whether or not to act on this method. The memory manager will
+ /// typically allocate all sections as read-write and then apply specific
+ /// permissions when this method is called. Code sections cannot be executed
+ /// until this function has been called. In addition, any cache coherency
+ /// operations needed to reliably use the memory are also performed.
+ ///
+ /// \returns true if an error occurred, false otherwise.
+ bool finalizeMemory(std::string *ErrMsg = nullptr) override;
+
+ /// Invalidate instruction cache for code sections.
+ ///
+ /// Some platforms with separate data cache and instruction cache require
+ /// explicit cache flush, otherwise JIT code manipulations (like resolved
+ /// relocations) will get to the data cache but not to the instruction cache.
+ ///
+ /// This method is called from finalizeMemory.
+ virtual void invalidateInstructionCache();
+
+private:
+ struct FreeMemBlock {
+ // The actual block of free memory
+ sys::MemoryBlock Free;
+ // If there is a pending allocation from the same reservation right before
+ // this block, store it's index in PendingMem, to be able to update the
+ // pending region if part of this block is allocated, rather than having to
+ // create a new one
+ unsigned PendingPrefixIndex;
+ };
+
+ struct MemoryGroup {
+ // PendingMem contains all blocks of memory (subblocks of AllocatedMem)
+ // which have not yet had their permissions applied, but have been given
+ // out to the user. FreeMem contains all block of memory, which have
+ // neither had their permissions applied, nor been given out to the user.
+ SmallVector<sys::MemoryBlock, 16> PendingMem;
+ SmallVector<FreeMemBlock, 16> FreeMem;
+
+ // All memory blocks that have been requested from the system
+ SmallVector<sys::MemoryBlock, 16> AllocatedMem;
+
+ sys::MemoryBlock Near;
+ };
+
+ uint8_t *allocateSection(AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment);
+
+ std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions);
+
+ bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
+
+ void anchor() override;
+
+ MemoryGroup CodeMem;
+ MemoryGroup RWDataMem;
+ MemoryGroup RODataMem;
+ MemoryMapper *MMapper;
+ std::unique_ptr<MemoryMapper> OwnedMMapper;
+ bool ReserveAllocation;
+};
+
+} // end namespace backport
+} // end namespace llvm
+
+#endif
+
+#endif // LLVM_EXECUTIONENGINE_BACKPORT_SAFESECTIONMEMORYMANAGER_H
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..fc10cd7543c 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -17,6 +17,9 @@
*/
#ifdef USE_LLVM
+#if LLVM_VERSION_MAJOR > 11
+#include <llvm-c/OrcEE.h>
+#endif
#include <llvm-c/Types.h>
@@ -135,6 +138,9 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+#if LLVM_VERSION_MAJOR > 11
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES);
+#endif
#ifdef __cplusplus
} /* extern "C" */
--
2.39.3 (Apple Git-146)
Slightly better version, which wraps the conditional code in #ifdef
USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER.
Attachments:
v5-0001-Backport-of-LLVM-code-to-fix-ARM-relocation-bug.patchapplication/octet-stream; name=v5-0001-Backport-of-LLVM-code-to-fix-ARM-relocation-bug.patchDownload
From e92904acb50b5aa6daed6e33f5ad81062fa34c38 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: [PATCH v5] Backport of LLVM code to fix ARM relocation bug.
Supply a new memory manager for RuntimeDyld that avoids putting ARM code
too far apart. This is the code from
https://github.com/llvm/llvm-project/pull/71968, copied into our tree
and moved into a new namespace llvm::backport, with minor adjustments to
work on LLVM 12-18.
This should fix the spate of crashes we've been receiving lately from
users on ARM systems.
XXX Ideally the LLVM project will commit this, and then we can resync
with the code in the LLVM 19.x stable branch, instead of using the code
from their PR, before we ship it!
Reported-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Reviewed-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Discussion: https://postgr.es/m/CAO6_Xqr63qj%3DSx7HY6ZiiQ6R_JbX%2B-p6sTPwDYwTWZjUmjsYBg%40mail.gmail.com
---
src/backend/jit/llvm/Makefile | 3 +-
src/backend/jit/llvm/SectionMemoryManager.cpp | 394 ++++++++++++++++++
src/backend/jit/llvm/llvmjit.c | 7 +
src/backend/jit/llvm/llvmjit_wrap.cpp | 20 +
src/backend/jit/llvm/meson.build | 1 +
src/include/jit/SectionMemoryManager.h | 227 ++++++++++
src/include/jit/llvmjit.h | 8 +
src/include/jit/llvmjit_backport.h | 20 +
8 files changed, 679 insertions(+), 1 deletion(-)
create mode 100644 src/backend/jit/llvm/SectionMemoryManager.cpp
create mode 100644 src/include/jit/SectionMemoryManager.h
create mode 100644 src/include/jit/llvmjit_backport.h
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
index bfe5c207a2b..e8c12060b93 100644
--- a/src/backend/jit/llvm/Makefile
+++ b/src/backend/jit/llvm/Makefile
@@ -47,7 +47,8 @@ OBJS += \
llvmjit.o \
llvmjit_error.o \
llvmjit_inline.o \
- llvmjit_wrap.o
+ llvmjit_wrap.o \
+ SectionMemoryManager.o
# Code generation
OBJS += \
diff --git a/src/backend/jit/llvm/SectionMemoryManager.cpp b/src/backend/jit/llvm/SectionMemoryManager.cpp
new file mode 100644
index 00000000000..f88ca3412e2
--- /dev/null
+++ b/src/backend/jit/llvm/SectionMemoryManager.cpp
@@ -0,0 +1,394 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the section-based memory manager used by the MCJIT
+// execution engine and RuntimeDyld
+//
+//===----------------------------------------------------------------------===//
+
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+
+#include "jit/SectionMemoryManager.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Process.h"
+
+namespace llvm {
+namespace backport {
+
+bool SectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
+ uintptr_t Size) const {
+ for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= Size)
+ return true;
+ }
+ return false;
+}
+
+#if LLVM_VERSION_MAJOR < 16
+void SectionMemoryManager::reserveAllocationSpace(uintptr_t CodeSize,
+ uint32_t CodeAlign_i,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign_i,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign_i) {
+ Align CodeAlign(CodeAlign_i);
+ Align RODataAlign(RODataAlign_i);
+ Align RWDataAlign(RWDataAlign_i);
+#else
+void SectionMemoryManager::reserveAllocationSpace(
+ uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
+ Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
+#endif
+ if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
+ return;
+
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ // Code alignment needs to be at least the stub alignment - however, we
+ // don't have an easy way to get that here so as a workaround, we assume
+ // it's 8, which is the largest value I observed across all platforms.
+ constexpr uint64_t StubAlign = 8;
+ CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
+ RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
+ RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
+
+ // Get space required for each section. Use the same calculation as
+ // allocateSection because we need to be able to satisfy it.
+ uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
+ uint64_t RequiredRODataSize =
+ alignTo(RODataSize, RODataAlign) + RODataAlign.value();
+ uint64_t RequiredRWDataSize =
+ alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
+
+ if (hasSpace(CodeMem, RequiredCodeSize) &&
+ hasSpace(RODataMem, RequiredRODataSize) &&
+ hasSpace(RWDataMem, RequiredRWDataSize)) {
+ // Sufficient space in contiguous block already available.
+ return;
+ }
+
+ // MemoryManager does not have functions for releasing memory after it's
+ // allocated. Normally it tries to use any excess blocks that were allocated
+ // due to page alignment, but if we have insufficient free memory for the
+ // request this can lead to allocating disparate memory that can violate the
+ // ARM ABI. Clear free memory so only the new allocations are used, but do
+ // not release allocated memory as it may still be in-use.
+ CodeMem.FreeMem.clear();
+ RODataMem.FreeMem.clear();
+ RWDataMem.FreeMem.clear();
+
+ // Round up to the nearest page size. Blocks must be page-aligned.
+ RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
+ RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
+ RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
+ uint64_t RequiredSize =
+ RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
+
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ AllocationPurpose::RWData, RequiredSize, nullptr,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ return;
+ }
+ // CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
+ CodeMem.AllocatedMem.push_back(MB);
+ uintptr_t Addr = (uintptr_t)MB.base();
+ FreeMemBlock FreeMB;
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+
+ if (CodeSize > 0) {
+ assert(isAddrAligned(CodeAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
+ CodeMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredCodeSize;
+ }
+
+ if (RODataSize > 0) {
+ assert(isAddrAligned(RODataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
+ RODataMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredRODataSize;
+ }
+
+ if (RWDataSize > 0) {
+ assert(isAddrAligned(RWDataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
+ RWDataMem.FreeMem.push_back(FreeMB);
+ }
+}
+
+uint8_t *SectionMemoryManager::allocateDataSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName,
+ bool IsReadOnly) {
+ if (IsReadOnly)
+ return allocateSection(SectionMemoryManager::AllocationPurpose::ROData,
+ Size, Alignment);
+ return allocateSection(SectionMemoryManager::AllocationPurpose::RWData, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateCodeSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) {
+ return allocateSection(SectionMemoryManager::AllocationPurpose::Code, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateSection(
+ SectionMemoryManager::AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment) {
+ if (!Alignment)
+ Alignment = 16;
+
+ assert(!(Alignment & (Alignment - 1)) && "Alignment must be a power of two.");
+
+ uintptr_t RequiredSize = Alignment * ((Size + Alignment - 1) / Alignment + 1);
+ uintptr_t Addr = 0;
+
+ MemoryGroup &MemGroup = [&]() -> MemoryGroup & {
+ switch (Purpose) {
+ case AllocationPurpose::Code:
+ return CodeMem;
+ case AllocationPurpose::ROData:
+ return RODataMem;
+ case AllocationPurpose::RWData:
+ return RWDataMem;
+ }
+ llvm_unreachable("Unknown SectionMemoryManager::AllocationPurpose");
+ }();
+
+ // Look in the list of free memory regions and use a block there if one
+ // is available.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= RequiredSize) {
+ Addr = (uintptr_t)FreeMB.Free.base();
+ uintptr_t EndOfBlock = Addr + FreeMB.Free.allocatedSize();
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ if (FreeMB.PendingPrefixIndex == (unsigned)-1) {
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // Remember this pending block, such that future allocations can just
+ // modify it rather than creating a new one
+ FreeMB.PendingPrefixIndex = MemGroup.PendingMem.size() - 1;
+ } else {
+ sys::MemoryBlock &PendingMB =
+ MemGroup.PendingMem[FreeMB.PendingPrefixIndex];
+ PendingMB = sys::MemoryBlock(PendingMB.base(),
+ Addr + Size - (uintptr_t)PendingMB.base());
+ }
+
+ // Remember how much free space is now left in this block
+ FreeMB.Free =
+ sys::MemoryBlock((void *)(Addr + Size), EndOfBlock - Addr - Size);
+ return (uint8_t *)Addr;
+ }
+ }
+
+ // No pre-allocated free block was large enough. Allocate a new memory region.
+ // Note that all sections get allocated as read-write. The permissions will
+ // be updated later based on memory group.
+ //
+ // FIXME: It would be useful to define a default allocation size (or add
+ // it as a constructor parameter) to minimize the number of allocations.
+ //
+ // FIXME: Initialize the Near member for each memory group to avoid
+ // interleaving.
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ Purpose, RequiredSize, &MemGroup.Near,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ // FIXME: Add error propagation to the interface.
+ return nullptr;
+ }
+
+ // Save this address as the basis for our next request
+ MemGroup.Near = MB;
+
+ // Copy the address to all the other groups, if they have not
+ // been initialized.
+ if (CodeMem.Near.base() == nullptr)
+ CodeMem.Near = MB;
+ if (RODataMem.Near.base() == nullptr)
+ RODataMem.Near = MB;
+ if (RWDataMem.Near.base() == nullptr)
+ RWDataMem.Near = MB;
+
+ // Remember that we allocated this memory
+ MemGroup.AllocatedMem.push_back(MB);
+ Addr = (uintptr_t)MB.base();
+ uintptr_t EndOfBlock = Addr + MB.allocatedSize();
+
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // The allocateMappedMemory may allocate much more memory than we need. In
+ // this case, we store the unused memory as a free memory block.
+ unsigned FreeSize = EndOfBlock - Addr - Size;
+ if (FreeSize > 16) {
+ FreeMemBlock FreeMB;
+ FreeMB.Free = sys::MemoryBlock((void *)(Addr + Size), FreeSize);
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ MemGroup.FreeMem.push_back(FreeMB);
+ }
+
+ // Return aligned address
+ return (uint8_t *)Addr;
+}
+
+bool SectionMemoryManager::finalizeMemory(std::string *ErrMsg) {
+ // FIXME: Should in-progress permissions be reverted if an error occurs?
+ std::error_code ec;
+
+ // Make code memory executable.
+ ec = applyMemoryGroupPermissions(CodeMem,
+ sys::Memory::MF_READ | sys::Memory::MF_EXEC);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Make read-only data memory read-only.
+ ec = applyMemoryGroupPermissions(RODataMem, sys::Memory::MF_READ);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Read-write data memory already has the correct permissions
+
+ // Some platforms with separate data cache and instruction cache require
+ // explicit cache flush, otherwise JIT code manipulations (like resolved
+ // relocations) will get to the data cache but not to the instruction cache.
+ invalidateInstructionCache();
+
+ return false;
+}
+
+static sys::MemoryBlock trimBlockToPageSize(sys::MemoryBlock M) {
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ size_t StartOverlap =
+ (PageSize - ((uintptr_t)M.base() % PageSize)) % PageSize;
+
+ size_t TrimmedSize = M.allocatedSize();
+ TrimmedSize -= StartOverlap;
+ TrimmedSize -= TrimmedSize % PageSize;
+
+ sys::MemoryBlock Trimmed((void *)((uintptr_t)M.base() + StartOverlap),
+ TrimmedSize);
+
+ assert(((uintptr_t)Trimmed.base() % PageSize) == 0);
+ assert((Trimmed.allocatedSize() % PageSize) == 0);
+ assert(M.base() <= Trimmed.base() &&
+ Trimmed.allocatedSize() <= M.allocatedSize());
+
+ return Trimmed;
+}
+
+std::error_code
+SectionMemoryManager::applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions) {
+ for (sys::MemoryBlock &MB : MemGroup.PendingMem)
+ if (std::error_code EC = MMapper->protectMappedMemory(MB, Permissions))
+ return EC;
+
+ MemGroup.PendingMem.clear();
+
+ // Now go through free blocks and trim any of them that don't span the entire
+ // page because one of the pending blocks may have overlapped it.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ FreeMB.Free = trimBlockToPageSize(FreeMB.Free);
+ // We cleared the PendingMem list, so all these pointers are now invalid
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ }
+
+ // Remove all blocks which are now empty
+ erase_if(MemGroup.FreeMem, [](FreeMemBlock &FreeMB) {
+ return FreeMB.Free.allocatedSize() == 0;
+ });
+
+ return std::error_code();
+}
+
+void SectionMemoryManager::invalidateInstructionCache() {
+ for (sys::MemoryBlock &Block : CodeMem.PendingMem)
+ sys::Memory::InvalidateInstructionCache(Block.base(),
+ Block.allocatedSize());
+}
+
+SectionMemoryManager::~SectionMemoryManager() {
+ for (MemoryGroup *Group : {&CodeMem, &RWDataMem, &RODataMem}) {
+ for (sys::MemoryBlock &Block : Group->AllocatedMem)
+ MMapper->releaseMappedMemory(Block);
+ }
+}
+
+SectionMemoryManager::MemoryMapper::~MemoryMapper() = default;
+
+void SectionMemoryManager::anchor() {}
+
+namespace {
+// Trivial implementation of SectionMemoryManager::MemoryMapper that just calls
+// into sys::Memory.
+class DefaultMMapper final : public SectionMemoryManager::MemoryMapper {
+public:
+ sys::MemoryBlock
+ allocateMappedMemory(SectionMemoryManager::AllocationPurpose Purpose,
+ size_t NumBytes, const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) override {
+ return sys::Memory::allocateMappedMemory(NumBytes, NearBlock, Flags, EC);
+ }
+
+ std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) override {
+ return sys::Memory::protectMappedMemory(Block, Flags);
+ }
+
+ std::error_code releaseMappedMemory(sys::MemoryBlock &M) override {
+ return sys::Memory::releaseMappedMemory(M);
+ }
+};
+} // namespace
+
+SectionMemoryManager::SectionMemoryManager(MemoryMapper *UnownedMM,
+ bool ReserveAlloc)
+ : MMapper(UnownedMM), OwnedMMapper(nullptr),
+ ReserveAllocation(ReserveAlloc) {
+ if (!MMapper) {
+ OwnedMMapper = std::make_unique<DefaultMMapper>();
+ MMapper = OwnedMMapper.get();
+ }
+}
+
+} // namespace backport
+} // namespace llvm
+
+#endif
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 0f6cec52496..92e6e16063d 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -38,6 +38,7 @@
#endif
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
#include "jit/llvmjit_emit.h"
#include "miscadmin.h"
#include "portability/instr_time.h"
@@ -1278,8 +1279,14 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error)
static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+ LLVMOrcObjectLayerRef objlayer =
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+#else
LLVMOrcObjectLayerRef objlayer =
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+#endif
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 641c8841ca3..1d061cfaa84 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -24,6 +24,14 @@ extern "C"
#include <llvm/IR/Function.h>
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
+#include "jit/SectionMemoryManager.h"
+#include <llvm/Support/CBindingWrapping.h>
+#endif
/*
@@ -41,3 +49,15 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<llvm::backport::SectionMemoryManager>(nullptr, true); }));
+}
+#endif
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index 4a4232661ba..b76e2e70547 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -14,6 +14,7 @@ llvmjit_sources += files(
'llvmjit_error.cpp',
'llvmjit_inline.cpp',
'llvmjit_wrap.cpp',
+ 'SectionMemoryManager.cpp',
)
# Code generation
diff --git a/src/include/jit/SectionMemoryManager.h b/src/include/jit/SectionMemoryManager.h
new file mode 100644
index 00000000000..d644ff921dc
--- /dev/null
+++ b/src/include/jit/SectionMemoryManager.h
@@ -0,0 +1,227 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.h - Memory manager for MCJIT/RtDyld -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of a section-based memory manager used by
+// the MCJIT execution engine and RuntimeDyld.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+#define LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Memory.h"
+#include <cstdint>
+#include <string>
+#include <system_error>
+
+namespace llvm {
+namespace backport {
+
+/// This is a simple memory manager which implements the methods called by
+/// the RuntimeDyld class to allocate memory for section-based loading of
+/// objects, usually those generated by the MCJIT execution engine.
+///
+/// This memory manager allocates all section memory as read-write. The
+/// RuntimeDyld will copy JITed section memory into these allocated blocks
+/// and perform any necessary linking and relocations.
+///
+/// Any client using this memory manager MUST ensure that section-specific
+/// page permissions have been applied before attempting to execute functions
+/// in the JITed object. Permissions can be applied either by calling
+/// MCJIT::finalizeObject or by calling SectionMemoryManager::finalizeMemory
+/// directly. Clients of MCJIT should call MCJIT::finalizeObject.
+class SectionMemoryManager : public RTDyldMemoryManager {
+public:
+ /// This enum describes the various reasons to allocate pages from
+ /// allocateMappedMemory.
+ enum class AllocationPurpose {
+ Code,
+ ROData,
+ RWData,
+ };
+
+ /// Implementations of this interface are used by SectionMemoryManager to
+ /// request pages from the operating system.
+ class MemoryMapper {
+ public:
+ /// This method attempts to allocate \p NumBytes bytes of virtual memory for
+ /// \p Purpose. \p NearBlock may point to an existing allocation, in which
+ /// case an attempt is made to allocate more memory near the existing block.
+ /// The actual allocated address is not guaranteed to be near the requested
+ /// address. \p Flags is used to set the initial protection flags for the
+ /// block of the memory. \p EC [out] returns an object describing any error
+ /// that occurs.
+ ///
+ /// This method may allocate more than the number of bytes requested. The
+ /// actual number of bytes allocated is indicated in the returned
+ /// MemoryBlock.
+ ///
+ /// The start of the allocated block must be aligned with the system
+ /// allocation granularity (64K on Windows, page size on Linux). If the
+ /// address following \p NearBlock is not so aligned, it will be rounded up
+ /// to the next allocation granularity boundary.
+ ///
+ /// \r a non-null MemoryBlock if the function was successful, otherwise a
+ /// null MemoryBlock with \p EC describing the error.
+ virtual sys::MemoryBlock
+ allocateMappedMemory(AllocationPurpose Purpose, size_t NumBytes,
+ const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) = 0;
+
+ /// This method sets the protection flags for a block of memory to the state
+ /// specified by \p Flags. The behavior is not specified if the memory was
+ /// not allocated using the allocateMappedMemory method.
+ /// \p Block describes the memory block to be protected.
+ /// \p Flags specifies the new protection state to be assigned to the block.
+ ///
+ /// If \p Flags is MF_WRITE, the actual behavior varies with the operating
+ /// system (i.e. MF_READ | MF_WRITE on Windows) and the target architecture
+ /// (i.e. MF_WRITE -> MF_READ | MF_WRITE on i386).
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) = 0;
+
+ /// This method releases a block of memory that was allocated with the
+ /// allocateMappedMemory method. It should not be used to release any memory
+ /// block allocated any other way.
+ /// \p Block describes the memory to be released.
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code releaseMappedMemory(sys::MemoryBlock &M) = 0;
+
+ virtual ~MemoryMapper();
+ };
+
+ /// Creates a SectionMemoryManager instance with \p MM as the associated
+ /// memory mapper. If \p MM is nullptr then a default memory mapper is used
+ /// that directly calls into the operating system.
+ ///
+ /// If \p ReserveAlloc is true all memory will be pre-allocated, and any
+ /// attempts to allocate beyond pre-allocated memory will fail.
+ SectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
+ SectionMemoryManager(const SectionMemoryManager &) = delete;
+ void operator=(const SectionMemoryManager &) = delete;
+ ~SectionMemoryManager() override;
+
+ /// Enable reserveAllocationSpace when requested.
+ bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
+
+ /// Implements allocating all memory in a single block. This is required to
+ /// limit memory offsets to fit the ARM ABI; large memory systems may
+ /// otherwise allocate separate sections too far apart.
+#if LLVM_VERSION_MAJOR < 16
+ virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign) override;
+#else
+ void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
+ uintptr_t RODataSize, Align RODataAlign,
+ uintptr_t RWDataSize, Align RWDataAlign) override;
+#endif
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) override;
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID, StringRef SectionName,
+ bool isReadOnly) override;
+
+ /// Update section-specific memory permissions and other attributes.
+ ///
+ /// This method is called when object loading is complete and section page
+ /// permissions can be applied. It is up to the memory manager implementation
+ /// to decide whether or not to act on this method. The memory manager will
+ /// typically allocate all sections as read-write and then apply specific
+ /// permissions when this method is called. Code sections cannot be executed
+ /// until this function has been called. In addition, any cache coherency
+ /// operations needed to reliably use the memory are also performed.
+ ///
+ /// \returns true if an error occurred, false otherwise.
+ bool finalizeMemory(std::string *ErrMsg = nullptr) override;
+
+ /// Invalidate instruction cache for code sections.
+ ///
+ /// Some platforms with separate data cache and instruction cache require
+ /// explicit cache flush, otherwise JIT code manipulations (like resolved
+ /// relocations) will get to the data cache but not to the instruction cache.
+ ///
+ /// This method is called from finalizeMemory.
+ virtual void invalidateInstructionCache();
+
+private:
+ struct FreeMemBlock {
+ // The actual block of free memory
+ sys::MemoryBlock Free;
+ // If there is a pending allocation from the same reservation right before
+ // this block, store it's index in PendingMem, to be able to update the
+ // pending region if part of this block is allocated, rather than having to
+ // create a new one
+ unsigned PendingPrefixIndex;
+ };
+
+ struct MemoryGroup {
+ // PendingMem contains all blocks of memory (subblocks of AllocatedMem)
+ // which have not yet had their permissions applied, but have been given
+ // out to the user. FreeMem contains all block of memory, which have
+ // neither had their permissions applied, nor been given out to the user.
+ SmallVector<sys::MemoryBlock, 16> PendingMem;
+ SmallVector<FreeMemBlock, 16> FreeMem;
+
+ // All memory blocks that have been requested from the system
+ SmallVector<sys::MemoryBlock, 16> AllocatedMem;
+
+ sys::MemoryBlock Near;
+ };
+
+ uint8_t *allocateSection(AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment);
+
+ std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions);
+
+ bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
+
+ void anchor() override;
+
+ MemoryGroup CodeMem;
+ MemoryGroup RWDataMem;
+ MemoryGroup RODataMem;
+ MemoryMapper *MMapper;
+ std::unique_ptr<MemoryMapper> OwnedMMapper;
+ bool ReserveAllocation;
+};
+
+} // end namespace backport
+} // end namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..dc0d86f4c1d 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -17,7 +17,12 @@
*/
#ifdef USE_LLVM
+#include "jit/llvmjit_backport.h"
+
#include <llvm-c/Types.h>
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm-c/OrcEE.h>
+#endif
/*
@@ -135,6 +140,9 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES);
+#endif
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/include/jit/llvmjit_backport.h b/src/include/jit/llvmjit_backport.h
new file mode 100644
index 00000000000..10b7b5cae87
--- /dev/null
+++ b/src/include/jit/llvmjit_backport.h
@@ -0,0 +1,20 @@
+/*
+ * A small header than can be included by backported LLVM code or PostgreSQL
+ * code, to control conditional compilation.
+ */
+#ifndef LLVMJIT_BACKPORT_H
+#define LLVMJIT_BACKPORT_H
+
+#include <llvm/Config/llvm-config.h>
+
+/*
+ * LLVM RuntimeDyld can produce code that crashes on larger memory ARM systems,
+ * due to memory placement. As a workaround, we supply an alternative memory
+ * manager class from https://github.com/llvm/llvm-project/pull/71968, but
+ * we've only backported it as far as LLVM 12.
+ */
+#if defined(__aarch64__) && LLVM_VERSION_MAJOR > 11
+#define USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#endif
+
+#endif
--
2.39.3 (Apple Git-146)
On Wed, Aug 28, 2024 at 12:24 AM Thomas Munro <thomas.munro@gmail.com> wrote:
2. I tested against LLVM 10-18, and found that 10 and 11 lack some
needed symbols. So I just hid this code from them. Even though our
stable branches support those and even older versions, I am not sure
if it's worth trying to do something about that for EOL'd distros that
no one has ever complained about. I am willing to try harder if
someone thinks that's important...
I would also assume that people using arm64 are more likely to use
recent versions than not.
I've done some additional tests on different LLVM versions with both
the unpatched version (to make sure the crash was triggered) and the
patched version. I'm joining the test scripts I've used as reference.
They target a kubernetes pod since it was the easiest way for me to
get a test ubuntu Jammy:
- setup_pod.sh: Install necessary packages, get multiple llvm
versions, fetch and compile master and patched version of postgres on
different LLVM version
- run_test.sh: go through all LLVM versions for both unpatched and
patched postgres to run the test_script.sh
- test_script.sh: ran inside the pod to setup the db with the
necessary tables and check if the crash happens
This generated the following output:
Test unpatched version on LLVM 19, : Crash triggered
Test unpatched version on LLVM 18, libLLVM-18.so.18.1: Crash triggered
Test unpatched version on LLVM 17, libLLVM-17.so.1: Crash triggered
Test unpatched version on LLVM 16, libLLVM-16.so.1: Crash triggered
Test unpatched version on LLVM 15, libLLVM-15.so.1: Crash triggered
Test unpatched version on LLVM 14, libLLVM-14.so.1: Crash triggered
Test unpatched version on LLVM 13, libLLVM-13.so.1: Crash triggered
Test patched version on LLVM 19, : Query ran successfully
Test patched version on LLVM 18, libLLVM-18.so.18.1: Query ran successfully
Test patched version on LLVM 17, libLLVM-17.so.1: Query ran successfully
Test patched version on LLVM 16, libLLVM-16.so.1: Query ran successfully
Test patched version on LLVM 15, libLLVM-15.so.1: Query ran successfully
Test patched version on LLVM 14, libLLVM-14.so.1: Query ran successfully
Test patched version on LLVM 13, libLLVM-13.so.1: Query ran successfully
I try to print the libLLVM linked to llvm.jit in the output to double
check whether I test on the correct version. The LLVM 19 package only
provides static libraries (probably because it's still a release
candidate?) so it shows as empty in the output. There was no LLVM 12
available when using the llvm.sh script so I couldn't test it. As for
the result, prepatch PG all crashed as expected while the patched
version was able to run the query successfully.
Next, I think we should wait to see if the LLVM project commits that
PR, this so that we can sync with their 19.x stable branch, instead of
using code from a PR. Our next minor release is in November, so we
have some time. If they don't commit it, we can consider it anyway: I
mean, it's crashing all over the place in production, and we see that
other projects are shipping this code already.
The PR[1]https://github.com/llvm/llvm-project/pull/71968 just received an approval and it sounds like they are ok to
eventually merge it.
I created a commitfest entry[1]https://commitfest.postgresql.org/49/5220/ to have the CI test the patch. There
was a failure in headerscheck and cpluspluscheck when the include of
SectionMemoryManager.h is checked[2]https://cirrus-ci.com/task/4646639124611072?logs=headers_headerscheck#L42-L46
In file included from /usr/include/llvm/ADT/SmallVector.h:18,
from /tmp/cirrus-ci-build/src/include/jit/SectionMemoryManager.h:23,
from /tmp/headerscheck.4b1i5C/test.c:2:
/usr/include/llvm/Support/type_traits.h:17:10: fatal error:
type_traits: No such file or directory
17 | #include <type_traits>
Since the SmallVector.h include type_traits, this file can't be
compiled with a C compiler so I've just excluded it from headerscheck.
Loosely related to headerscheck, running it locally was failing as it
couldn't find the <llvm-c/Core.h> file. This is because headerscheck
except llvm include files to be in /usr/include and don't rely on
llvm-config. I created a second patch to use the LLVM_CPPFLAGS as
extra flags when testing the src/include/jit/* files.
Lastly, I've used www.github.com instead of github.com link to stop
spamming the llvm-project's PR with reference to the commit every time
it is pushed somewhere (which seems to be the unofficial hack[3]https://github.com/orgs/community/discussions/23123#discussioncomment-3239240).
[1]: https://commitfest.postgresql.org/49/5220/
[2]: https://cirrus-ci.com/task/4646639124611072?logs=headers_headerscheck#L42-L46
[3]: https://github.com/orgs/community/discussions/23123#discussioncomment-3239240
Attachments:
v6-0001-Backport-of-LLVM-code-to-fix-ARM-relocation-bug.patchapplication/octet-stream; name=v6-0001-Backport-of-LLVM-code-to-fix-ARM-relocation-bug.patchDownload
From 9c2b7632fae1eb1bb562ae99b609c2d3be41f1e4 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: Backport of LLVM code to fix ARM relocation bug.
Supply a new memory manager for RuntimeDyld that avoids putting ARM code
too far apart. This is the code from
https://www.github.com/llvm/llvm-project/pull/71968, copied into our tree
and moved into a new namespace llvm::backport, with minor adjustments to
work on LLVM 12-18.
This should fix the spate of crashes we've been receiving lately from
users on ARM systems.
XXX Ideally the LLVM project will commit this, and then we can resync
with the code in the LLVM 19.x stable branch, instead of using the code
from their PR, before we ship it!
Reported-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Reviewed-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Discussion: https://postgr.es/m/CAO6_Xqr63qj%3DSx7HY6ZiiQ6R_JbX%2B-p6sTPwDYwTWZjUmjsYBg%40mail.gmail.com
Signed-off-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
---
src/backend/jit/llvm/Makefile | 3 +-
src/backend/jit/llvm/SectionMemoryManager.cpp | 394 ++++++++++++++++++
src/backend/jit/llvm/llvmjit.c | 7 +
src/backend/jit/llvm/llvmjit_wrap.cpp | 20 +
src/backend/jit/llvm/meson.build | 1 +
src/include/jit/SectionMemoryManager.h | 227 ++++++++++
src/include/jit/llvmjit.h | 8 +
src/include/jit/llvmjit_backport.h | 20 +
src/tools/pginclude/headerscheck | 5 +
9 files changed, 684 insertions(+), 1 deletion(-)
create mode 100644 src/backend/jit/llvm/SectionMemoryManager.cpp
create mode 100644 src/include/jit/SectionMemoryManager.h
create mode 100644 src/include/jit/llvmjit_backport.h
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
index bfe5c207a2b..e8c12060b93 100644
--- a/src/backend/jit/llvm/Makefile
+++ b/src/backend/jit/llvm/Makefile
@@ -47,7 +47,8 @@ OBJS += \
llvmjit.o \
llvmjit_error.o \
llvmjit_inline.o \
- llvmjit_wrap.o
+ llvmjit_wrap.o \
+ SectionMemoryManager.o
# Code generation
OBJS += \
diff --git a/src/backend/jit/llvm/SectionMemoryManager.cpp b/src/backend/jit/llvm/SectionMemoryManager.cpp
new file mode 100644
index 00000000000..f88ca3412e2
--- /dev/null
+++ b/src/backend/jit/llvm/SectionMemoryManager.cpp
@@ -0,0 +1,394 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the section-based memory manager used by the MCJIT
+// execution engine and RuntimeDyld
+//
+//===----------------------------------------------------------------------===//
+
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+
+#include "jit/SectionMemoryManager.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Process.h"
+
+namespace llvm {
+namespace backport {
+
+bool SectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
+ uintptr_t Size) const {
+ for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= Size)
+ return true;
+ }
+ return false;
+}
+
+#if LLVM_VERSION_MAJOR < 16
+void SectionMemoryManager::reserveAllocationSpace(uintptr_t CodeSize,
+ uint32_t CodeAlign_i,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign_i,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign_i) {
+ Align CodeAlign(CodeAlign_i);
+ Align RODataAlign(RODataAlign_i);
+ Align RWDataAlign(RWDataAlign_i);
+#else
+void SectionMemoryManager::reserveAllocationSpace(
+ uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
+ Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
+#endif
+ if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
+ return;
+
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ // Code alignment needs to be at least the stub alignment - however, we
+ // don't have an easy way to get that here so as a workaround, we assume
+ // it's 8, which is the largest value I observed across all platforms.
+ constexpr uint64_t StubAlign = 8;
+ CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
+ RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
+ RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
+
+ // Get space required for each section. Use the same calculation as
+ // allocateSection because we need to be able to satisfy it.
+ uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
+ uint64_t RequiredRODataSize =
+ alignTo(RODataSize, RODataAlign) + RODataAlign.value();
+ uint64_t RequiredRWDataSize =
+ alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
+
+ if (hasSpace(CodeMem, RequiredCodeSize) &&
+ hasSpace(RODataMem, RequiredRODataSize) &&
+ hasSpace(RWDataMem, RequiredRWDataSize)) {
+ // Sufficient space in contiguous block already available.
+ return;
+ }
+
+ // MemoryManager does not have functions for releasing memory after it's
+ // allocated. Normally it tries to use any excess blocks that were allocated
+ // due to page alignment, but if we have insufficient free memory for the
+ // request this can lead to allocating disparate memory that can violate the
+ // ARM ABI. Clear free memory so only the new allocations are used, but do
+ // not release allocated memory as it may still be in-use.
+ CodeMem.FreeMem.clear();
+ RODataMem.FreeMem.clear();
+ RWDataMem.FreeMem.clear();
+
+ // Round up to the nearest page size. Blocks must be page-aligned.
+ RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
+ RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
+ RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
+ uint64_t RequiredSize =
+ RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
+
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ AllocationPurpose::RWData, RequiredSize, nullptr,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ return;
+ }
+ // CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
+ CodeMem.AllocatedMem.push_back(MB);
+ uintptr_t Addr = (uintptr_t)MB.base();
+ FreeMemBlock FreeMB;
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+
+ if (CodeSize > 0) {
+ assert(isAddrAligned(CodeAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
+ CodeMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredCodeSize;
+ }
+
+ if (RODataSize > 0) {
+ assert(isAddrAligned(RODataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
+ RODataMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredRODataSize;
+ }
+
+ if (RWDataSize > 0) {
+ assert(isAddrAligned(RWDataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
+ RWDataMem.FreeMem.push_back(FreeMB);
+ }
+}
+
+uint8_t *SectionMemoryManager::allocateDataSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName,
+ bool IsReadOnly) {
+ if (IsReadOnly)
+ return allocateSection(SectionMemoryManager::AllocationPurpose::ROData,
+ Size, Alignment);
+ return allocateSection(SectionMemoryManager::AllocationPurpose::RWData, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateCodeSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) {
+ return allocateSection(SectionMemoryManager::AllocationPurpose::Code, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateSection(
+ SectionMemoryManager::AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment) {
+ if (!Alignment)
+ Alignment = 16;
+
+ assert(!(Alignment & (Alignment - 1)) && "Alignment must be a power of two.");
+
+ uintptr_t RequiredSize = Alignment * ((Size + Alignment - 1) / Alignment + 1);
+ uintptr_t Addr = 0;
+
+ MemoryGroup &MemGroup = [&]() -> MemoryGroup & {
+ switch (Purpose) {
+ case AllocationPurpose::Code:
+ return CodeMem;
+ case AllocationPurpose::ROData:
+ return RODataMem;
+ case AllocationPurpose::RWData:
+ return RWDataMem;
+ }
+ llvm_unreachable("Unknown SectionMemoryManager::AllocationPurpose");
+ }();
+
+ // Look in the list of free memory regions and use a block there if one
+ // is available.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= RequiredSize) {
+ Addr = (uintptr_t)FreeMB.Free.base();
+ uintptr_t EndOfBlock = Addr + FreeMB.Free.allocatedSize();
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ if (FreeMB.PendingPrefixIndex == (unsigned)-1) {
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // Remember this pending block, such that future allocations can just
+ // modify it rather than creating a new one
+ FreeMB.PendingPrefixIndex = MemGroup.PendingMem.size() - 1;
+ } else {
+ sys::MemoryBlock &PendingMB =
+ MemGroup.PendingMem[FreeMB.PendingPrefixIndex];
+ PendingMB = sys::MemoryBlock(PendingMB.base(),
+ Addr + Size - (uintptr_t)PendingMB.base());
+ }
+
+ // Remember how much free space is now left in this block
+ FreeMB.Free =
+ sys::MemoryBlock((void *)(Addr + Size), EndOfBlock - Addr - Size);
+ return (uint8_t *)Addr;
+ }
+ }
+
+ // No pre-allocated free block was large enough. Allocate a new memory region.
+ // Note that all sections get allocated as read-write. The permissions will
+ // be updated later based on memory group.
+ //
+ // FIXME: It would be useful to define a default allocation size (or add
+ // it as a constructor parameter) to minimize the number of allocations.
+ //
+ // FIXME: Initialize the Near member for each memory group to avoid
+ // interleaving.
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ Purpose, RequiredSize, &MemGroup.Near,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ // FIXME: Add error propagation to the interface.
+ return nullptr;
+ }
+
+ // Save this address as the basis for our next request
+ MemGroup.Near = MB;
+
+ // Copy the address to all the other groups, if they have not
+ // been initialized.
+ if (CodeMem.Near.base() == nullptr)
+ CodeMem.Near = MB;
+ if (RODataMem.Near.base() == nullptr)
+ RODataMem.Near = MB;
+ if (RWDataMem.Near.base() == nullptr)
+ RWDataMem.Near = MB;
+
+ // Remember that we allocated this memory
+ MemGroup.AllocatedMem.push_back(MB);
+ Addr = (uintptr_t)MB.base();
+ uintptr_t EndOfBlock = Addr + MB.allocatedSize();
+
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // The allocateMappedMemory may allocate much more memory than we need. In
+ // this case, we store the unused memory as a free memory block.
+ unsigned FreeSize = EndOfBlock - Addr - Size;
+ if (FreeSize > 16) {
+ FreeMemBlock FreeMB;
+ FreeMB.Free = sys::MemoryBlock((void *)(Addr + Size), FreeSize);
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ MemGroup.FreeMem.push_back(FreeMB);
+ }
+
+ // Return aligned address
+ return (uint8_t *)Addr;
+}
+
+bool SectionMemoryManager::finalizeMemory(std::string *ErrMsg) {
+ // FIXME: Should in-progress permissions be reverted if an error occurs?
+ std::error_code ec;
+
+ // Make code memory executable.
+ ec = applyMemoryGroupPermissions(CodeMem,
+ sys::Memory::MF_READ | sys::Memory::MF_EXEC);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Make read-only data memory read-only.
+ ec = applyMemoryGroupPermissions(RODataMem, sys::Memory::MF_READ);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Read-write data memory already has the correct permissions
+
+ // Some platforms with separate data cache and instruction cache require
+ // explicit cache flush, otherwise JIT code manipulations (like resolved
+ // relocations) will get to the data cache but not to the instruction cache.
+ invalidateInstructionCache();
+
+ return false;
+}
+
+static sys::MemoryBlock trimBlockToPageSize(sys::MemoryBlock M) {
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ size_t StartOverlap =
+ (PageSize - ((uintptr_t)M.base() % PageSize)) % PageSize;
+
+ size_t TrimmedSize = M.allocatedSize();
+ TrimmedSize -= StartOverlap;
+ TrimmedSize -= TrimmedSize % PageSize;
+
+ sys::MemoryBlock Trimmed((void *)((uintptr_t)M.base() + StartOverlap),
+ TrimmedSize);
+
+ assert(((uintptr_t)Trimmed.base() % PageSize) == 0);
+ assert((Trimmed.allocatedSize() % PageSize) == 0);
+ assert(M.base() <= Trimmed.base() &&
+ Trimmed.allocatedSize() <= M.allocatedSize());
+
+ return Trimmed;
+}
+
+std::error_code
+SectionMemoryManager::applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions) {
+ for (sys::MemoryBlock &MB : MemGroup.PendingMem)
+ if (std::error_code EC = MMapper->protectMappedMemory(MB, Permissions))
+ return EC;
+
+ MemGroup.PendingMem.clear();
+
+ // Now go through free blocks and trim any of them that don't span the entire
+ // page because one of the pending blocks may have overlapped it.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ FreeMB.Free = trimBlockToPageSize(FreeMB.Free);
+ // We cleared the PendingMem list, so all these pointers are now invalid
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ }
+
+ // Remove all blocks which are now empty
+ erase_if(MemGroup.FreeMem, [](FreeMemBlock &FreeMB) {
+ return FreeMB.Free.allocatedSize() == 0;
+ });
+
+ return std::error_code();
+}
+
+void SectionMemoryManager::invalidateInstructionCache() {
+ for (sys::MemoryBlock &Block : CodeMem.PendingMem)
+ sys::Memory::InvalidateInstructionCache(Block.base(),
+ Block.allocatedSize());
+}
+
+SectionMemoryManager::~SectionMemoryManager() {
+ for (MemoryGroup *Group : {&CodeMem, &RWDataMem, &RODataMem}) {
+ for (sys::MemoryBlock &Block : Group->AllocatedMem)
+ MMapper->releaseMappedMemory(Block);
+ }
+}
+
+SectionMemoryManager::MemoryMapper::~MemoryMapper() = default;
+
+void SectionMemoryManager::anchor() {}
+
+namespace {
+// Trivial implementation of SectionMemoryManager::MemoryMapper that just calls
+// into sys::Memory.
+class DefaultMMapper final : public SectionMemoryManager::MemoryMapper {
+public:
+ sys::MemoryBlock
+ allocateMappedMemory(SectionMemoryManager::AllocationPurpose Purpose,
+ size_t NumBytes, const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) override {
+ return sys::Memory::allocateMappedMemory(NumBytes, NearBlock, Flags, EC);
+ }
+
+ std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) override {
+ return sys::Memory::protectMappedMemory(Block, Flags);
+ }
+
+ std::error_code releaseMappedMemory(sys::MemoryBlock &M) override {
+ return sys::Memory::releaseMappedMemory(M);
+ }
+};
+} // namespace
+
+SectionMemoryManager::SectionMemoryManager(MemoryMapper *UnownedMM,
+ bool ReserveAlloc)
+ : MMapper(UnownedMM), OwnedMMapper(nullptr),
+ ReserveAllocation(ReserveAlloc) {
+ if (!MMapper) {
+ OwnedMMapper = std::make_unique<DefaultMMapper>();
+ MMapper = OwnedMMapper.get();
+ }
+}
+
+} // namespace backport
+} // namespace llvm
+
+#endif
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 0f6cec52496..92e6e16063d 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -38,6 +38,7 @@
#endif
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
#include "jit/llvmjit_emit.h"
#include "miscadmin.h"
#include "portability/instr_time.h"
@@ -1278,8 +1279,14 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error)
static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+ LLVMOrcObjectLayerRef objlayer =
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+#else
LLVMOrcObjectLayerRef objlayer =
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+#endif
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 641c8841ca3..1d061cfaa84 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -24,6 +24,14 @@ extern "C"
#include <llvm/IR/Function.h>
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
+#include "jit/SectionMemoryManager.h"
+#include <llvm/Support/CBindingWrapping.h>
+#endif
/*
@@ -41,3 +49,15 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<llvm::backport::SectionMemoryManager>(nullptr, true); }));
+}
+#endif
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index 4a4232661ba..b76e2e70547 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -14,6 +14,7 @@ llvmjit_sources += files(
'llvmjit_error.cpp',
'llvmjit_inline.cpp',
'llvmjit_wrap.cpp',
+ 'SectionMemoryManager.cpp',
)
# Code generation
diff --git a/src/include/jit/SectionMemoryManager.h b/src/include/jit/SectionMemoryManager.h
new file mode 100644
index 00000000000..d644ff921dc
--- /dev/null
+++ b/src/include/jit/SectionMemoryManager.h
@@ -0,0 +1,227 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.h - Memory manager for MCJIT/RtDyld -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of a section-based memory manager used by
+// the MCJIT execution engine and RuntimeDyld.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+#define LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Memory.h"
+#include <cstdint>
+#include <string>
+#include <system_error>
+
+namespace llvm {
+namespace backport {
+
+/// This is a simple memory manager which implements the methods called by
+/// the RuntimeDyld class to allocate memory for section-based loading of
+/// objects, usually those generated by the MCJIT execution engine.
+///
+/// This memory manager allocates all section memory as read-write. The
+/// RuntimeDyld will copy JITed section memory into these allocated blocks
+/// and perform any necessary linking and relocations.
+///
+/// Any client using this memory manager MUST ensure that section-specific
+/// page permissions have been applied before attempting to execute functions
+/// in the JITed object. Permissions can be applied either by calling
+/// MCJIT::finalizeObject or by calling SectionMemoryManager::finalizeMemory
+/// directly. Clients of MCJIT should call MCJIT::finalizeObject.
+class SectionMemoryManager : public RTDyldMemoryManager {
+public:
+ /// This enum describes the various reasons to allocate pages from
+ /// allocateMappedMemory.
+ enum class AllocationPurpose {
+ Code,
+ ROData,
+ RWData,
+ };
+
+ /// Implementations of this interface are used by SectionMemoryManager to
+ /// request pages from the operating system.
+ class MemoryMapper {
+ public:
+ /// This method attempts to allocate \p NumBytes bytes of virtual memory for
+ /// \p Purpose. \p NearBlock may point to an existing allocation, in which
+ /// case an attempt is made to allocate more memory near the existing block.
+ /// The actual allocated address is not guaranteed to be near the requested
+ /// address. \p Flags is used to set the initial protection flags for the
+ /// block of the memory. \p EC [out] returns an object describing any error
+ /// that occurs.
+ ///
+ /// This method may allocate more than the number of bytes requested. The
+ /// actual number of bytes allocated is indicated in the returned
+ /// MemoryBlock.
+ ///
+ /// The start of the allocated block must be aligned with the system
+ /// allocation granularity (64K on Windows, page size on Linux). If the
+ /// address following \p NearBlock is not so aligned, it will be rounded up
+ /// to the next allocation granularity boundary.
+ ///
+ /// \r a non-null MemoryBlock if the function was successful, otherwise a
+ /// null MemoryBlock with \p EC describing the error.
+ virtual sys::MemoryBlock
+ allocateMappedMemory(AllocationPurpose Purpose, size_t NumBytes,
+ const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) = 0;
+
+ /// This method sets the protection flags for a block of memory to the state
+ /// specified by \p Flags. The behavior is not specified if the memory was
+ /// not allocated using the allocateMappedMemory method.
+ /// \p Block describes the memory block to be protected.
+ /// \p Flags specifies the new protection state to be assigned to the block.
+ ///
+ /// If \p Flags is MF_WRITE, the actual behavior varies with the operating
+ /// system (i.e. MF_READ | MF_WRITE on Windows) and the target architecture
+ /// (i.e. MF_WRITE -> MF_READ | MF_WRITE on i386).
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) = 0;
+
+ /// This method releases a block of memory that was allocated with the
+ /// allocateMappedMemory method. It should not be used to release any memory
+ /// block allocated any other way.
+ /// \p Block describes the memory to be released.
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code releaseMappedMemory(sys::MemoryBlock &M) = 0;
+
+ virtual ~MemoryMapper();
+ };
+
+ /// Creates a SectionMemoryManager instance with \p MM as the associated
+ /// memory mapper. If \p MM is nullptr then a default memory mapper is used
+ /// that directly calls into the operating system.
+ ///
+ /// If \p ReserveAlloc is true all memory will be pre-allocated, and any
+ /// attempts to allocate beyond pre-allocated memory will fail.
+ SectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
+ SectionMemoryManager(const SectionMemoryManager &) = delete;
+ void operator=(const SectionMemoryManager &) = delete;
+ ~SectionMemoryManager() override;
+
+ /// Enable reserveAllocationSpace when requested.
+ bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
+
+ /// Implements allocating all memory in a single block. This is required to
+ /// limit memory offsets to fit the ARM ABI; large memory systems may
+ /// otherwise allocate separate sections too far apart.
+#if LLVM_VERSION_MAJOR < 16
+ virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign) override;
+#else
+ void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
+ uintptr_t RODataSize, Align RODataAlign,
+ uintptr_t RWDataSize, Align RWDataAlign) override;
+#endif
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) override;
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID, StringRef SectionName,
+ bool isReadOnly) override;
+
+ /// Update section-specific memory permissions and other attributes.
+ ///
+ /// This method is called when object loading is complete and section page
+ /// permissions can be applied. It is up to the memory manager implementation
+ /// to decide whether or not to act on this method. The memory manager will
+ /// typically allocate all sections as read-write and then apply specific
+ /// permissions when this method is called. Code sections cannot be executed
+ /// until this function has been called. In addition, any cache coherency
+ /// operations needed to reliably use the memory are also performed.
+ ///
+ /// \returns true if an error occurred, false otherwise.
+ bool finalizeMemory(std::string *ErrMsg = nullptr) override;
+
+ /// Invalidate instruction cache for code sections.
+ ///
+ /// Some platforms with separate data cache and instruction cache require
+ /// explicit cache flush, otherwise JIT code manipulations (like resolved
+ /// relocations) will get to the data cache but not to the instruction cache.
+ ///
+ /// This method is called from finalizeMemory.
+ virtual void invalidateInstructionCache();
+
+private:
+ struct FreeMemBlock {
+ // The actual block of free memory
+ sys::MemoryBlock Free;
+ // If there is a pending allocation from the same reservation right before
+ // this block, store it's index in PendingMem, to be able to update the
+ // pending region if part of this block is allocated, rather than having to
+ // create a new one
+ unsigned PendingPrefixIndex;
+ };
+
+ struct MemoryGroup {
+ // PendingMem contains all blocks of memory (subblocks of AllocatedMem)
+ // which have not yet had their permissions applied, but have been given
+ // out to the user. FreeMem contains all block of memory, which have
+ // neither had their permissions applied, nor been given out to the user.
+ SmallVector<sys::MemoryBlock, 16> PendingMem;
+ SmallVector<FreeMemBlock, 16> FreeMem;
+
+ // All memory blocks that have been requested from the system
+ SmallVector<sys::MemoryBlock, 16> AllocatedMem;
+
+ sys::MemoryBlock Near;
+ };
+
+ uint8_t *allocateSection(AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment);
+
+ std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions);
+
+ bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
+
+ void anchor() override;
+
+ MemoryGroup CodeMem;
+ MemoryGroup RWDataMem;
+ MemoryGroup RODataMem;
+ MemoryMapper *MMapper;
+ std::unique_ptr<MemoryMapper> OwnedMMapper;
+ bool ReserveAllocation;
+};
+
+} // end namespace backport
+} // end namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..dc0d86f4c1d 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -17,7 +17,12 @@
*/
#ifdef USE_LLVM
+#include "jit/llvmjit_backport.h"
+
#include <llvm-c/Types.h>
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm-c/OrcEE.h>
+#endif
/*
@@ -135,6 +140,9 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES);
+#endif
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/include/jit/llvmjit_backport.h b/src/include/jit/llvmjit_backport.h
new file mode 100644
index 00000000000..10b7b5cae87
--- /dev/null
+++ b/src/include/jit/llvmjit_backport.h
@@ -0,0 +1,20 @@
+/*
+ * A small header than can be included by backported LLVM code or PostgreSQL
+ * code, to control conditional compilation.
+ */
+#ifndef LLVMJIT_BACKPORT_H
+#define LLVMJIT_BACKPORT_H
+
+#include <llvm/Config/llvm-config.h>
+
+/*
+ * LLVM RuntimeDyld can produce code that crashes on larger memory ARM systems,
+ * due to memory placement. As a workaround, we supply an alternative memory
+ * manager class from https://github.com/llvm/llvm-project/pull/71968, but
+ * we've only backported it as far as LLVM 12.
+ */
+#if defined(__aarch64__) && LLVM_VERSION_MAJOR > 11
+#define USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#endif
+
+#endif
diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck
index 436e2b92a33..a78e16888a6 100755
--- a/src/tools/pginclude/headerscheck
+++ b/src/tools/pginclude/headerscheck
@@ -162,6 +162,11 @@ do
# This produces a "no previous prototype" warning.
! $cplusplus && test "$f" = src/include/storage/checksum_impl.h && continue
+ # SectionMemoryManager.h is C++ code which includes <type_traits> through
+ # llvm/ADT/SmallVector.h so it won't compile with a C compiler neither with
+ # a C++ compiler under extern "C" linkage
+ test "$f" = src/include/jit/SectionMemoryManager.h && continue
+
# ppport.h is not under our control, so we can't make it standalone.
test "$f" = src/pl/plperl/ppport.h && continue
--
2.39.3 (Apple Git-146)
v6-0002-Add-LLVM_CPPFLAGS-in-headerscheck-to-llvm-jit-fil.patchapplication/octet-stream; name=v6-0002-Add-LLVM_CPPFLAGS-in-headerscheck-to-llvm-jit-fil.patchDownload
From c5dd8e23416e09aa63dea2ab574a42b7f30b7567 Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Date: Fri, 30 Aug 2024 14:45:30 +0200
Subject: Add LLVM_CPPFLAGS in headerscheck to llvm jit files
headerscheck on the CI currently works because the required llvm include
files are located in /usr/include (like /usr/include/llvm-c/Core.h).
However, it is very likely that those files will be located in a version
specific directory and llvm-config should be used to get the correct
cppflag using "$LLVM_CONFIG --cppflags".
Those flags are available in configure as LLVM_CPPFLAGS so we can add
them as extra flags for the headerscheck test command. This will allow
to run headerscheck on systems that don't have llvm include files in
/usr/include
---
src/tools/pginclude/headerscheck | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck
index a78e16888a6..59fa4363ab7 100755
--- a/src/tools/pginclude/headerscheck
+++ b/src/tools/pginclude/headerscheck
@@ -43,6 +43,7 @@ CXXFLAGS=${CXXFLAGS:- -fsyntax-only -Wall}
# Pull some info from configure's results.
MGLOB="$builddir/src/Makefile.global"
CPPFLAGS=`sed -n 's/^CPPFLAGS[ ]*=[ ]*//p' "$MGLOB"`
+LLVM_CPPFLAGS=`sed -n 's/^LLVM_CPPFLAGS[ ]*=[ ]*//p' "$MGLOB"`
CFLAGS=`sed -n 's/^CFLAGS[ ]*=[ ]*//p' "$MGLOB"`
CC=`sed -n 's/^CC[ ]*=[ ]*//p' "$MGLOB"`
CXX=`sed -n 's/^CXX[ ]*=[ ]*//p' "$MGLOB"`
@@ -236,6 +237,8 @@ do
EXTRAINCLUDES="$python_includespec" ;;
src/interfaces/ecpg/*)
EXTRAINCLUDES="-I $builddir/src/interfaces/ecpg/include -I $srcdir/src/interfaces/ecpg/include" ;;
+ src/include/jit/*)
+ EXTRAINCLUDES="$LLVM_CPPFLAGS" ;;
src/backend/parser/*)
EXTRAINCLUDES="-I $builddir/src/backend/parser/" ;;
src/backend/utils/adt/*)
--
2.39.3 (Apple Git-146)
I've run some additional tests, mostly pgbench with
options=-cjit_above_cost=0 for an extended duration on an instance
that was impacted. I haven't seen any issues nor performance
regressions compared to the unpatched version.
I will switch the commitfest entry to Ready for Committer if there's
no objection.
On Thu, Oct 17, 2024 at 10:36 PM Anthonin Bonnefoy
<anthonin.bonnefoy@datadoghq.com> wrote:
I've run some additional tests, mostly pgbench with
options=-cjit_above_cost=0 for an extended duration on an instance
that was impacted. I haven't seen any issues nor performance
regressions compared to the unpatched version.I will switch the commitfest entry to Ready for Committer if there's
no objection.
Thanks! I'm going to go ahead and commit this. I asked Andres if he
wanted to object to this plan (as author of our LLVM stuff) and he did
not. I tried, for several frustrating days, to figure out how to
solve our problem using JITLink and stay on a more "supported" path,
but it all seems a bit unready, as various things don't work or aren't
available in various versions in our support range. At least I now
have the bones of a patch to prepare for JITLink in LLVM 20 or
whenever they force our hand... I'll write about that in a new thread
soon.
On Thu, Oct 17, 2024 at 10:41 PM Thomas Munro <thomas.munro@gmail.com> wrote:
Thanks! I'm going to go ahead and commit this.
(Sorry for the delay, I got distracted by pgconf.eu.)
Today I set out to commit this patch, and wrote a proper commit
message to explain the code provenance, circumstances that led to it,
and the future conditions that will allow us to delete it in a few
years. Please see attached. In the process I struck a potential
snag:
https://llvm.org/LICENSE.txt
https://en.wikipedia.org/wiki/Apache_License
There are a couple of cases of dual-licensed code in our tree where we
explicitly used the Boost alternative instead of Apache 2. I plead
complete ignorance of this topic and defer to those who know about
such things: can we actually do this? I guess at a minimum a copy of
the licence would need to appear somewhere -- perhaps under
src/backend/jit/llvm? 4d says that if you modified the code you have
to say so prominently, but I did that at the top (and the changes are
completely trivial, just some #ifdef swizzling to massage some
function prototypes to suit older LLVMs). Otherwise I understand it
to be generally "BSD-like" (sans advert clause) but there is also some
stuff about patents, which surely aren't relevant to this in
practice... but I know that some projects object to it on principle
and because it smells like contract law, or something.... not an area
I am well informed about. Who should I be asking? (Naively, I
wondered: could there be some kind of fair use concept for
back-patching fixes to broken libraries that you're merely a user of
where you can be excused from the burdens of a distributor? Yeah
wishful thinking I'm sure.)
Attachments:
v7-0001-Monkey-patch-LLVM-code-to-fix-ARM-relocation-bug.patchtext/x-patch; charset=US-ASCII; name=v7-0001-Monkey-patch-LLVM-code-to-fix-ARM-relocation-bug.patchDownload
From 5d9ef5f83a79f56ddd6230c7e9428525bab0d82c Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: [PATCH v7] Monkey-patch LLVM code to fix ARM relocation bug.
Supply a new memory manager for RuntimeDyld that avoids overflowing the
jump distance in linked code on large memory machines. This is the same
Apache v2-licensed code that is normally used inside the LLVM library,
except this copy has been patched by Michael Smith in his proposed fix
https://www.github.com/llvm/llvm-project/pull/71968.
We hereby slurp it into our own source tree, after moving into a new
namespace llvm::backport and making some minor adjustments so that it
can be compiled with older LLVM versions as far back as 12. It's harder
to make it work on even older LLVM versions, but it doesn't seem likely
that people are really using them so that is not investigated for now.
(We were probably remiss in not disclaiming support for ancient LLVM
releases sooner; starting from PostgreSQL 16 we've been more agressive
about trimming version support to match the trailing end of common
software distributions).
The problem could also have been addressed by switching to JITLink, and
that is the LLVM project's recommended solution as RuntimeDyld is about
to be deprecated. We'll have to do that soon enough anyway, and then
when the LLVM version support window advances far enough in a few years
we'll be able to delete this code. Unfortunately that wouldn't work
today for some relevant releases of LLVM where JITLink is missing or
incomplete.
Several other projects have already taken the approach of back-porting
this fix into their fork of LLVM, which is a vote of confidence despite
the lack of commit into LLVM as of today. We don't have our own copy of
LLVM so we can't do exactly what they've done. We just pick up the
whole patched class and inject an instance of it into the regular LLVM
code.
The changes that we've had to make to our copy can be seen by diffing
our SectionMemoryManager.{h,cpp} files against the ones in the tree of
the pull request.
The LLVM project hasn't chosen to commit the fix yet, and even it it
did, it wouldn't be back-ported into the releases of LLVM that most of
our users care about, so there is not much point in waiting any longer
for that. If they make further changes and commit it to LLVM 19 or 20,
we'll still need this for older versions, but we may want to
resynchronize our copy.
This should fix the spate of crash reports we've been receiving lately
from users on large memory ARM systems.
Back-patch to all supported releases.
Co-authored-by: Thomas Munro <thomas.munro@gmail.com>
Co-authored-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Reported-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Discussion: https://postgr.es/m/CAO6_Xqr63qj%3DSx7HY6ZiiQ6R_JbX%2B-p6sTPwDYwTWZjUmjsYBg%40mail.gmail.com
---
src/backend/jit/llvm/Makefile | 3 +-
src/backend/jit/llvm/SectionMemoryManager.cpp | 394 ++++++++++++++++++
src/backend/jit/llvm/llvmjit.c | 7 +
src/backend/jit/llvm/llvmjit_wrap.cpp | 20 +
src/backend/jit/llvm/meson.build | 1 +
src/include/jit/SectionMemoryManager.h | 227 ++++++++++
src/include/jit/llvmjit.h | 8 +
src/include/jit/llvmjit_backport.h | 20 +
src/tools/pginclude/headerscheck | 3 +
9 files changed, 682 insertions(+), 1 deletion(-)
create mode 100644 src/backend/jit/llvm/SectionMemoryManager.cpp
create mode 100644 src/include/jit/SectionMemoryManager.h
create mode 100644 src/include/jit/llvmjit_backport.h
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
index bfe5c207a2b..e8c12060b93 100644
--- a/src/backend/jit/llvm/Makefile
+++ b/src/backend/jit/llvm/Makefile
@@ -47,7 +47,8 @@ OBJS += \
llvmjit.o \
llvmjit_error.o \
llvmjit_inline.o \
- llvmjit_wrap.o
+ llvmjit_wrap.o \
+ SectionMemoryManager.o
# Code generation
OBJS += \
diff --git a/src/backend/jit/llvm/SectionMemoryManager.cpp b/src/backend/jit/llvm/SectionMemoryManager.cpp
new file mode 100644
index 00000000000..f88ca3412e2
--- /dev/null
+++ b/src/backend/jit/llvm/SectionMemoryManager.cpp
@@ -0,0 +1,394 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the section-based memory manager used by the MCJIT
+// execution engine and RuntimeDyld
+//
+//===----------------------------------------------------------------------===//
+
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+
+#include "jit/SectionMemoryManager.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Process.h"
+
+namespace llvm {
+namespace backport {
+
+bool SectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
+ uintptr_t Size) const {
+ for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= Size)
+ return true;
+ }
+ return false;
+}
+
+#if LLVM_VERSION_MAJOR < 16
+void SectionMemoryManager::reserveAllocationSpace(uintptr_t CodeSize,
+ uint32_t CodeAlign_i,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign_i,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign_i) {
+ Align CodeAlign(CodeAlign_i);
+ Align RODataAlign(RODataAlign_i);
+ Align RWDataAlign(RWDataAlign_i);
+#else
+void SectionMemoryManager::reserveAllocationSpace(
+ uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
+ Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
+#endif
+ if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
+ return;
+
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ // Code alignment needs to be at least the stub alignment - however, we
+ // don't have an easy way to get that here so as a workaround, we assume
+ // it's 8, which is the largest value I observed across all platforms.
+ constexpr uint64_t StubAlign = 8;
+ CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
+ RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
+ RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
+
+ // Get space required for each section. Use the same calculation as
+ // allocateSection because we need to be able to satisfy it.
+ uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
+ uint64_t RequiredRODataSize =
+ alignTo(RODataSize, RODataAlign) + RODataAlign.value();
+ uint64_t RequiredRWDataSize =
+ alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
+
+ if (hasSpace(CodeMem, RequiredCodeSize) &&
+ hasSpace(RODataMem, RequiredRODataSize) &&
+ hasSpace(RWDataMem, RequiredRWDataSize)) {
+ // Sufficient space in contiguous block already available.
+ return;
+ }
+
+ // MemoryManager does not have functions for releasing memory after it's
+ // allocated. Normally it tries to use any excess blocks that were allocated
+ // due to page alignment, but if we have insufficient free memory for the
+ // request this can lead to allocating disparate memory that can violate the
+ // ARM ABI. Clear free memory so only the new allocations are used, but do
+ // not release allocated memory as it may still be in-use.
+ CodeMem.FreeMem.clear();
+ RODataMem.FreeMem.clear();
+ RWDataMem.FreeMem.clear();
+
+ // Round up to the nearest page size. Blocks must be page-aligned.
+ RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
+ RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
+ RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
+ uint64_t RequiredSize =
+ RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
+
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ AllocationPurpose::RWData, RequiredSize, nullptr,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ return;
+ }
+ // CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
+ CodeMem.AllocatedMem.push_back(MB);
+ uintptr_t Addr = (uintptr_t)MB.base();
+ FreeMemBlock FreeMB;
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+
+ if (CodeSize > 0) {
+ assert(isAddrAligned(CodeAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
+ CodeMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredCodeSize;
+ }
+
+ if (RODataSize > 0) {
+ assert(isAddrAligned(RODataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
+ RODataMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredRODataSize;
+ }
+
+ if (RWDataSize > 0) {
+ assert(isAddrAligned(RWDataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
+ RWDataMem.FreeMem.push_back(FreeMB);
+ }
+}
+
+uint8_t *SectionMemoryManager::allocateDataSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName,
+ bool IsReadOnly) {
+ if (IsReadOnly)
+ return allocateSection(SectionMemoryManager::AllocationPurpose::ROData,
+ Size, Alignment);
+ return allocateSection(SectionMemoryManager::AllocationPurpose::RWData, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateCodeSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) {
+ return allocateSection(SectionMemoryManager::AllocationPurpose::Code, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateSection(
+ SectionMemoryManager::AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment) {
+ if (!Alignment)
+ Alignment = 16;
+
+ assert(!(Alignment & (Alignment - 1)) && "Alignment must be a power of two.");
+
+ uintptr_t RequiredSize = Alignment * ((Size + Alignment - 1) / Alignment + 1);
+ uintptr_t Addr = 0;
+
+ MemoryGroup &MemGroup = [&]() -> MemoryGroup & {
+ switch (Purpose) {
+ case AllocationPurpose::Code:
+ return CodeMem;
+ case AllocationPurpose::ROData:
+ return RODataMem;
+ case AllocationPurpose::RWData:
+ return RWDataMem;
+ }
+ llvm_unreachable("Unknown SectionMemoryManager::AllocationPurpose");
+ }();
+
+ // Look in the list of free memory regions and use a block there if one
+ // is available.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= RequiredSize) {
+ Addr = (uintptr_t)FreeMB.Free.base();
+ uintptr_t EndOfBlock = Addr + FreeMB.Free.allocatedSize();
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ if (FreeMB.PendingPrefixIndex == (unsigned)-1) {
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // Remember this pending block, such that future allocations can just
+ // modify it rather than creating a new one
+ FreeMB.PendingPrefixIndex = MemGroup.PendingMem.size() - 1;
+ } else {
+ sys::MemoryBlock &PendingMB =
+ MemGroup.PendingMem[FreeMB.PendingPrefixIndex];
+ PendingMB = sys::MemoryBlock(PendingMB.base(),
+ Addr + Size - (uintptr_t)PendingMB.base());
+ }
+
+ // Remember how much free space is now left in this block
+ FreeMB.Free =
+ sys::MemoryBlock((void *)(Addr + Size), EndOfBlock - Addr - Size);
+ return (uint8_t *)Addr;
+ }
+ }
+
+ // No pre-allocated free block was large enough. Allocate a new memory region.
+ // Note that all sections get allocated as read-write. The permissions will
+ // be updated later based on memory group.
+ //
+ // FIXME: It would be useful to define a default allocation size (or add
+ // it as a constructor parameter) to minimize the number of allocations.
+ //
+ // FIXME: Initialize the Near member for each memory group to avoid
+ // interleaving.
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ Purpose, RequiredSize, &MemGroup.Near,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ // FIXME: Add error propagation to the interface.
+ return nullptr;
+ }
+
+ // Save this address as the basis for our next request
+ MemGroup.Near = MB;
+
+ // Copy the address to all the other groups, if they have not
+ // been initialized.
+ if (CodeMem.Near.base() == nullptr)
+ CodeMem.Near = MB;
+ if (RODataMem.Near.base() == nullptr)
+ RODataMem.Near = MB;
+ if (RWDataMem.Near.base() == nullptr)
+ RWDataMem.Near = MB;
+
+ // Remember that we allocated this memory
+ MemGroup.AllocatedMem.push_back(MB);
+ Addr = (uintptr_t)MB.base();
+ uintptr_t EndOfBlock = Addr + MB.allocatedSize();
+
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // The allocateMappedMemory may allocate much more memory than we need. In
+ // this case, we store the unused memory as a free memory block.
+ unsigned FreeSize = EndOfBlock - Addr - Size;
+ if (FreeSize > 16) {
+ FreeMemBlock FreeMB;
+ FreeMB.Free = sys::MemoryBlock((void *)(Addr + Size), FreeSize);
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ MemGroup.FreeMem.push_back(FreeMB);
+ }
+
+ // Return aligned address
+ return (uint8_t *)Addr;
+}
+
+bool SectionMemoryManager::finalizeMemory(std::string *ErrMsg) {
+ // FIXME: Should in-progress permissions be reverted if an error occurs?
+ std::error_code ec;
+
+ // Make code memory executable.
+ ec = applyMemoryGroupPermissions(CodeMem,
+ sys::Memory::MF_READ | sys::Memory::MF_EXEC);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Make read-only data memory read-only.
+ ec = applyMemoryGroupPermissions(RODataMem, sys::Memory::MF_READ);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Read-write data memory already has the correct permissions
+
+ // Some platforms with separate data cache and instruction cache require
+ // explicit cache flush, otherwise JIT code manipulations (like resolved
+ // relocations) will get to the data cache but not to the instruction cache.
+ invalidateInstructionCache();
+
+ return false;
+}
+
+static sys::MemoryBlock trimBlockToPageSize(sys::MemoryBlock M) {
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ size_t StartOverlap =
+ (PageSize - ((uintptr_t)M.base() % PageSize)) % PageSize;
+
+ size_t TrimmedSize = M.allocatedSize();
+ TrimmedSize -= StartOverlap;
+ TrimmedSize -= TrimmedSize % PageSize;
+
+ sys::MemoryBlock Trimmed((void *)((uintptr_t)M.base() + StartOverlap),
+ TrimmedSize);
+
+ assert(((uintptr_t)Trimmed.base() % PageSize) == 0);
+ assert((Trimmed.allocatedSize() % PageSize) == 0);
+ assert(M.base() <= Trimmed.base() &&
+ Trimmed.allocatedSize() <= M.allocatedSize());
+
+ return Trimmed;
+}
+
+std::error_code
+SectionMemoryManager::applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions) {
+ for (sys::MemoryBlock &MB : MemGroup.PendingMem)
+ if (std::error_code EC = MMapper->protectMappedMemory(MB, Permissions))
+ return EC;
+
+ MemGroup.PendingMem.clear();
+
+ // Now go through free blocks and trim any of them that don't span the entire
+ // page because one of the pending blocks may have overlapped it.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ FreeMB.Free = trimBlockToPageSize(FreeMB.Free);
+ // We cleared the PendingMem list, so all these pointers are now invalid
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ }
+
+ // Remove all blocks which are now empty
+ erase_if(MemGroup.FreeMem, [](FreeMemBlock &FreeMB) {
+ return FreeMB.Free.allocatedSize() == 0;
+ });
+
+ return std::error_code();
+}
+
+void SectionMemoryManager::invalidateInstructionCache() {
+ for (sys::MemoryBlock &Block : CodeMem.PendingMem)
+ sys::Memory::InvalidateInstructionCache(Block.base(),
+ Block.allocatedSize());
+}
+
+SectionMemoryManager::~SectionMemoryManager() {
+ for (MemoryGroup *Group : {&CodeMem, &RWDataMem, &RODataMem}) {
+ for (sys::MemoryBlock &Block : Group->AllocatedMem)
+ MMapper->releaseMappedMemory(Block);
+ }
+}
+
+SectionMemoryManager::MemoryMapper::~MemoryMapper() = default;
+
+void SectionMemoryManager::anchor() {}
+
+namespace {
+// Trivial implementation of SectionMemoryManager::MemoryMapper that just calls
+// into sys::Memory.
+class DefaultMMapper final : public SectionMemoryManager::MemoryMapper {
+public:
+ sys::MemoryBlock
+ allocateMappedMemory(SectionMemoryManager::AllocationPurpose Purpose,
+ size_t NumBytes, const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) override {
+ return sys::Memory::allocateMappedMemory(NumBytes, NearBlock, Flags, EC);
+ }
+
+ std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) override {
+ return sys::Memory::protectMappedMemory(Block, Flags);
+ }
+
+ std::error_code releaseMappedMemory(sys::MemoryBlock &M) override {
+ return sys::Memory::releaseMappedMemory(M);
+ }
+};
+} // namespace
+
+SectionMemoryManager::SectionMemoryManager(MemoryMapper *UnownedMM,
+ bool ReserveAlloc)
+ : MMapper(UnownedMM), OwnedMMapper(nullptr),
+ ReserveAllocation(ReserveAlloc) {
+ if (!MMapper) {
+ OwnedMMapper = std::make_unique<DefaultMMapper>();
+ MMapper = OwnedMMapper.get();
+ }
+}
+
+} // namespace backport
+} // namespace llvm
+
+#endif
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index f346af9c755..72411d0f805 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -34,6 +34,7 @@
#endif
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
#include "jit/llvmjit_emit.h"
#include "miscadmin.h"
#include "portability/instr_time.h"
@@ -1166,8 +1167,14 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error)
static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+ LLVMOrcObjectLayerRef objlayer =
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+#else
LLVMOrcObjectLayerRef objlayer =
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+#endif
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 7f7623dac64..9e8d63f0122 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -20,6 +20,14 @@ extern "C"
#include <llvm/IR/Function.h>
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
+#include "jit/SectionMemoryManager.h"
+#include <llvm/Support/CBindingWrapping.h>
+#endif
/*
@@ -37,3 +45,15 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<llvm::backport::SectionMemoryManager>(nullptr, true); }));
+}
+#endif
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index c1b6a9363eb..ce49043f1d0 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -14,6 +14,7 @@ llvmjit_sources += files(
'llvmjit_error.cpp',
'llvmjit_inline.cpp',
'llvmjit_wrap.cpp',
+ 'SectionMemoryManager.cpp',
)
# Code generation
diff --git a/src/include/jit/SectionMemoryManager.h b/src/include/jit/SectionMemoryManager.h
new file mode 100644
index 00000000000..d644ff921dc
--- /dev/null
+++ b/src/include/jit/SectionMemoryManager.h
@@ -0,0 +1,227 @@
+/*
+ * This file taken from https://github.com/llvm/llvm-project/pull/71968, with
+ * the name changed to llvm::backport::SectionMemoryManager, so we can support
+ * the ARM memory model on broken LLVM versions.
+ */
+
+//===- SectionMemoryManager.h - Memory manager for MCJIT/RtDyld -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of a section-based memory manager used by
+// the MCJIT execution engine and RuntimeDyld.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+#define LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Memory.h"
+#include <cstdint>
+#include <string>
+#include <system_error>
+
+namespace llvm {
+namespace backport {
+
+/// This is a simple memory manager which implements the methods called by
+/// the RuntimeDyld class to allocate memory for section-based loading of
+/// objects, usually those generated by the MCJIT execution engine.
+///
+/// This memory manager allocates all section memory as read-write. The
+/// RuntimeDyld will copy JITed section memory into these allocated blocks
+/// and perform any necessary linking and relocations.
+///
+/// Any client using this memory manager MUST ensure that section-specific
+/// page permissions have been applied before attempting to execute functions
+/// in the JITed object. Permissions can be applied either by calling
+/// MCJIT::finalizeObject or by calling SectionMemoryManager::finalizeMemory
+/// directly. Clients of MCJIT should call MCJIT::finalizeObject.
+class SectionMemoryManager : public RTDyldMemoryManager {
+public:
+ /// This enum describes the various reasons to allocate pages from
+ /// allocateMappedMemory.
+ enum class AllocationPurpose {
+ Code,
+ ROData,
+ RWData,
+ };
+
+ /// Implementations of this interface are used by SectionMemoryManager to
+ /// request pages from the operating system.
+ class MemoryMapper {
+ public:
+ /// This method attempts to allocate \p NumBytes bytes of virtual memory for
+ /// \p Purpose. \p NearBlock may point to an existing allocation, in which
+ /// case an attempt is made to allocate more memory near the existing block.
+ /// The actual allocated address is not guaranteed to be near the requested
+ /// address. \p Flags is used to set the initial protection flags for the
+ /// block of the memory. \p EC [out] returns an object describing any error
+ /// that occurs.
+ ///
+ /// This method may allocate more than the number of bytes requested. The
+ /// actual number of bytes allocated is indicated in the returned
+ /// MemoryBlock.
+ ///
+ /// The start of the allocated block must be aligned with the system
+ /// allocation granularity (64K on Windows, page size on Linux). If the
+ /// address following \p NearBlock is not so aligned, it will be rounded up
+ /// to the next allocation granularity boundary.
+ ///
+ /// \r a non-null MemoryBlock if the function was successful, otherwise a
+ /// null MemoryBlock with \p EC describing the error.
+ virtual sys::MemoryBlock
+ allocateMappedMemory(AllocationPurpose Purpose, size_t NumBytes,
+ const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) = 0;
+
+ /// This method sets the protection flags for a block of memory to the state
+ /// specified by \p Flags. The behavior is not specified if the memory was
+ /// not allocated using the allocateMappedMemory method.
+ /// \p Block describes the memory block to be protected.
+ /// \p Flags specifies the new protection state to be assigned to the block.
+ ///
+ /// If \p Flags is MF_WRITE, the actual behavior varies with the operating
+ /// system (i.e. MF_READ | MF_WRITE on Windows) and the target architecture
+ /// (i.e. MF_WRITE -> MF_READ | MF_WRITE on i386).
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) = 0;
+
+ /// This method releases a block of memory that was allocated with the
+ /// allocateMappedMemory method. It should not be used to release any memory
+ /// block allocated any other way.
+ /// \p Block describes the memory to be released.
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code releaseMappedMemory(sys::MemoryBlock &M) = 0;
+
+ virtual ~MemoryMapper();
+ };
+
+ /// Creates a SectionMemoryManager instance with \p MM as the associated
+ /// memory mapper. If \p MM is nullptr then a default memory mapper is used
+ /// that directly calls into the operating system.
+ ///
+ /// If \p ReserveAlloc is true all memory will be pre-allocated, and any
+ /// attempts to allocate beyond pre-allocated memory will fail.
+ SectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
+ SectionMemoryManager(const SectionMemoryManager &) = delete;
+ void operator=(const SectionMemoryManager &) = delete;
+ ~SectionMemoryManager() override;
+
+ /// Enable reserveAllocationSpace when requested.
+ bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
+
+ /// Implements allocating all memory in a single block. This is required to
+ /// limit memory offsets to fit the ARM ABI; large memory systems may
+ /// otherwise allocate separate sections too far apart.
+#if LLVM_VERSION_MAJOR < 16
+ virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign) override;
+#else
+ void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
+ uintptr_t RODataSize, Align RODataAlign,
+ uintptr_t RWDataSize, Align RWDataAlign) override;
+#endif
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) override;
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID, StringRef SectionName,
+ bool isReadOnly) override;
+
+ /// Update section-specific memory permissions and other attributes.
+ ///
+ /// This method is called when object loading is complete and section page
+ /// permissions can be applied. It is up to the memory manager implementation
+ /// to decide whether or not to act on this method. The memory manager will
+ /// typically allocate all sections as read-write and then apply specific
+ /// permissions when this method is called. Code sections cannot be executed
+ /// until this function has been called. In addition, any cache coherency
+ /// operations needed to reliably use the memory are also performed.
+ ///
+ /// \returns true if an error occurred, false otherwise.
+ bool finalizeMemory(std::string *ErrMsg = nullptr) override;
+
+ /// Invalidate instruction cache for code sections.
+ ///
+ /// Some platforms with separate data cache and instruction cache require
+ /// explicit cache flush, otherwise JIT code manipulations (like resolved
+ /// relocations) will get to the data cache but not to the instruction cache.
+ ///
+ /// This method is called from finalizeMemory.
+ virtual void invalidateInstructionCache();
+
+private:
+ struct FreeMemBlock {
+ // The actual block of free memory
+ sys::MemoryBlock Free;
+ // If there is a pending allocation from the same reservation right before
+ // this block, store it's index in PendingMem, to be able to update the
+ // pending region if part of this block is allocated, rather than having to
+ // create a new one
+ unsigned PendingPrefixIndex;
+ };
+
+ struct MemoryGroup {
+ // PendingMem contains all blocks of memory (subblocks of AllocatedMem)
+ // which have not yet had their permissions applied, but have been given
+ // out to the user. FreeMem contains all block of memory, which have
+ // neither had their permissions applied, nor been given out to the user.
+ SmallVector<sys::MemoryBlock, 16> PendingMem;
+ SmallVector<FreeMemBlock, 16> FreeMem;
+
+ // All memory blocks that have been requested from the system
+ SmallVector<sys::MemoryBlock, 16> AllocatedMem;
+
+ sys::MemoryBlock Near;
+ };
+
+ uint8_t *allocateSection(AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment);
+
+ std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions);
+
+ bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
+
+ void anchor() override;
+
+ MemoryGroup CodeMem;
+ MemoryGroup RWDataMem;
+ MemoryGroup RODataMem;
+ MemoryMapper *MMapper;
+ std::unique_ptr<MemoryMapper> OwnedMMapper;
+ bool ReserveAllocation;
+};
+
+} // end namespace backport
+} // end namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..dc0d86f4c1d 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -17,7 +17,12 @@
*/
#ifdef USE_LLVM
+#include "jit/llvmjit_backport.h"
+
#include <llvm-c/Types.h>
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm-c/OrcEE.h>
+#endif
/*
@@ -135,6 +140,9 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES);
+#endif
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/include/jit/llvmjit_backport.h b/src/include/jit/llvmjit_backport.h
new file mode 100644
index 00000000000..10b7b5cae87
--- /dev/null
+++ b/src/include/jit/llvmjit_backport.h
@@ -0,0 +1,20 @@
+/*
+ * A small header than can be included by backported LLVM code or PostgreSQL
+ * code, to control conditional compilation.
+ */
+#ifndef LLVMJIT_BACKPORT_H
+#define LLVMJIT_BACKPORT_H
+
+#include <llvm/Config/llvm-config.h>
+
+/*
+ * LLVM RuntimeDyld can produce code that crashes on larger memory ARM systems,
+ * due to memory placement. As a workaround, we supply an alternative memory
+ * manager class from https://github.com/llvm/llvm-project/pull/71968, but
+ * we've only backported it as far as LLVM 12.
+ */
+#if defined(__aarch64__) && LLVM_VERSION_MAJOR > 11
+#define USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#endif
+
+#endif
diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck
index 3fc737d2cc1..fb91b5760c4 100755
--- a/src/tools/pginclude/headerscheck
+++ b/src/tools/pginclude/headerscheck
@@ -168,6 +168,9 @@ do
# This produces a "no previous prototype" warning.
! $cplusplus && test "$f" = src/include/storage/checksum_impl.h && continue
+ # SectionMemoryManager.h is C++
+ test "$f" = src/include/jit/SectionMemoryManager.h && continue
+
# ppport.h is not under our control, so we can't make it standalone.
test "$f" = src/pl/plperl/ppport.h && continue
--
2.46.2
On Thu, Oct 31, 2024 at 6:49 AM Thomas Munro <thomas.munro@gmail.com> wrote:
There are a couple of cases of dual-licensed code in our tree where we
explicitly used the Boost alternative instead of Apache 2. I plead
complete ignorance of this topic and defer to those who know about
such things: can we actually do this? I guess at a minimum a copy of
the licence would need to appear somewhere -- perhaps under
src/backend/jit/llvm?
I'm also not super knowledgeable about the licensing intricacies but I
read it the same way - a license file has to be provided due to the 4a
clause. llvmlite did this when they added the patched memory
manager[1]https://github.com/numba/llvmlite/pull/1009/files#diff-80b149f35cebd583e21dfc49c0007a7fab89c3c6d07c028e4a87de0848aa2ed8
4d says that if you modified the code you have
to say so prominently, but I did that at the top (and the changes are
completely trivial, just some #ifdef swizzling to massage some
function prototypes to suit older LLVMs). Otherwise I understand it
to be generally "BSD-like" (sans advert clause) but there is also some
stuff about patents, which surely aren't relevant to this in
practice... but I know that some projects object to it on principle
and because it smells like contract law, or something.... not an area
I am well informed about. Who should I be asking? (Naively, I
wondered: could there be some kind of fair use concept for
back-patching fixes to broken libraries that you're merely a user of
where you can be excused from the burdens of a distributor? Yeah
wishful thinking I'm sure.)
You mean 4b, right? LLVM doesn't seem to have any NOTICE files so the
4d clause shouldn't apply. The top comment looks fine to notify the
source of the modified file and how it was changed. But again, I don't
have much experience in this so I can't be sure.
On 31 Oct 2024, at 06:48, Thomas Munro <thomas.munro@gmail.com> wrote:
I guess at a minimum a copy of the licence would need to appear somewhere
That's my interpretation of it as well.
perhaps under src/backend/jit/llvm?
Since SectionMemoryManager.h is in src/backend/jit I wonder if it should be a
placed in a section in src/backend/jit/README with an overview of the what and
why (or maybe a new src/backend/jit/llvm/README would be even better). The
license doesn't have to be in a separate file AFAICT and including a (version
of) your excellent summary in the commit message along with it would probably
help readers.
--
Daniel Gustafsson
On Thu, Oct 31, 2024 at 9:49 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 31 Oct 2024, at 06:48, Thomas Munro <thomas.munro@gmail.com> wrote:
I guess at a minimum a copy of the licence would need to appear somewhereThat's my interpretation of it as well.
perhaps under src/backend/jit/llvm?
Since SectionMemoryManager.h is in src/backend/jit I wonder if it should be a
placed in a section in src/backend/jit/README with an overview of the what and
why (or maybe a new src/backend/jit/llvm/README would be even better). The
license doesn't have to be in a separate file AFAICT and including a (version
of) your excellent summary in the commit message along with it would probably
help readers.
Thank you. I figured that
src/backend/jit/llvm/SectionMemoryManager.LICENSE would be a good
place, to make it clear which code it covers and to remember to delete
it when the time comes. If we ever have to do more of this sort of
thing we might want to rename it to something more general, but I hope
not! I also updated the comments with a briefer summary of the points
from the commit message, at the top of the .cpp file. I added a
pgindent exclusion for the new alien header, or else it gets
scrambled.
I was worried for a while about the C++14 code in here (eg
std::make_unique), something we've avoided using in the past, but
after a bit of 3D versionography I'm pretty sure there is no issue and
we don't have to contort the code to avoid it.
Reasoning: Old LLVM required C++11. LLVM 9 switched to C++14. LLVM
14 switched C++17. Pretty soon they'll flip to C++20 or C++23, they
don't mess around. The corresponding -std=c++XX flag finishes up in
our compile lines, because llvm-config --cxxflags spits it out, to
match the features they're using in headers that we include (easy to
spot examples being std::make_unique (C++14) and std::string_view
(C++17)), so you might say that PostgreSQL indirectly chases C++
standards much faster than it chases C standards. This particular
code is a special case because it's guarded for LLVM 12+ only, so it's
OK to use C++14 in that limited context even in back branches. We
have to be careful that it doesn't contain C++17 code since it came
from recent LLVM, but it doesn't seem to by inspection, and you can
check on a machine with CXX=g++ and LLVM 14 on Linux, which uses
-std=c++14 and fails if you add a use of <string_view> and
std::string_view. (Warning: the system C++ standard library on Macs
and other Clang-based systems doesn't have enough version guards so it
won't complain, but GCC and its standard library will explicitly tell
you not to use C++17 features in a C++14 program.)
If there are no further comments, I'm going to push this to all
branches tomorrow morning. For master only, I will remove the #if
condition and comment about LLVM 12+, as we now require 14+.
Attachments:
v8-0001-Monkey-patch-LLVM-code-to-fix-ARM-relocation-bug.patchapplication/x-patch; name=v8-0001-Monkey-patch-LLVM-code-to-fix-ARM-relocation-bug.patchDownload
From f81eb027042a35a8c93778526104a912a642c919 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 27 Aug 2024 08:58:48 +1200
Subject: [PATCH v8] Monkey-patch LLVM code to fix ARM relocation bug.
Supply a new memory manager for RuntimeDyld, to avoid crashes in
generated code caused by memory placement that can overflow a 32 bit
data type. This is a drop-in replacement for the
llvm::SectionMemoryManager class in the LLVM library, with patches from
Michael Smith's proposed fix at
https://www.github.com/llvm/llvm-project/pull/71968.
We hereby slurp it into our own source tree, after moving into a new
namespace llvm::backport and making some minor adjustments so that it
can be compiled with older LLVM versions as far back as 12. It's harder
to make it work on even older LLVM versions, but it doesn't seem likely
that people are really using them so that is not investigated for now.
The problem could also be addressed by switching to JITLink instead of
RuntimeDyld, and that is the LLVM project's recommended solution as
the latter is about to be deprecated. We'll have to do that soon enough
anyway, and then when the LLVM version support window advances far
enough in a few years we'll be able to delete this code. Unfortunately
that wouldn't be enough for PostgreSQL today: in most relevant versions
of LLVM, JITLink is missing or incomplete.
Several other projects have already back-ported this fix into their fork
of LLVM, which is a vote of confidence despite the lack of commit into
LLVM as of today. We don't have our own copy of LLVM so we can't do
exactly what they've done; instead we have a copy of the whole patched
class so we can pass an instance of it to RuntimeDyld.
The LLVM project hasn't chosen to commit the fix yet, and even if it
did, it wouldn't be back-ported into the releases of LLVM that most of
our users care about, so there is not much point in waiting any longer
for that. If they make further changes and commit it to LLVM 19 or 20,
we'll still need this for older versions, but we may want to
resynchronize our copy and update some comments.
The changes that we've had to make to our copy can be seen by diffing
our SectionMemoryManager.{h,cpp} files against the ones in the tree of
the pull request. Per the LLVM project's license requirements, a copy
is in SectionMemoryManager.LICENSE.
This should fix the spate of crash reports we've been receiving lately
from users on large memory ARM systems.
Back-patch to all supported releases.
Co-authored-by: Thomas Munro <thomas.munro@gmail.com>
Co-authored-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Reviewed-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Reviewed-by: Daniel Gustafsson <daniel@yesql.se> (license aspects)
Reported-by: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Discussion: https://postgr.es/m/CAO6_Xqr63qj%3DSx7HY6ZiiQ6R_JbX%2B-p6sTPwDYwTWZjUmjsYBg%40mail.gmail.com
---
src/backend/jit/llvm/Makefile | 3 +-
.../jit/llvm/SectionMemoryManager.LICENSE | 279 ++++++++++++
src/backend/jit/llvm/SectionMemoryManager.cpp | 412 ++++++++++++++++++
src/backend/jit/llvm/llvmjit.c | 7 +
src/backend/jit/llvm/llvmjit_wrap.cpp | 20 +
src/backend/jit/llvm/meson.build | 1 +
src/include/jit/SectionMemoryManager.h | 226 ++++++++++
src/include/jit/llvmjit.h | 8 +
src/include/jit/llvmjit_backport.h | 25 ++
src/tools/pginclude/headerscheck | 3 +
src/tools/pgindent/exclude_file_patterns | 3 +-
11 files changed, 985 insertions(+), 2 deletions(-)
create mode 100644 src/backend/jit/llvm/SectionMemoryManager.LICENSE
create mode 100644 src/backend/jit/llvm/SectionMemoryManager.cpp
create mode 100644 src/include/jit/SectionMemoryManager.h
create mode 100644 src/include/jit/llvmjit_backport.h
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
index bfe5c207a2b..e8c12060b93 100644
--- a/src/backend/jit/llvm/Makefile
+++ b/src/backend/jit/llvm/Makefile
@@ -47,7 +47,8 @@ OBJS += \
llvmjit.o \
llvmjit_error.o \
llvmjit_inline.o \
- llvmjit_wrap.o
+ llvmjit_wrap.o \
+ SectionMemoryManager.o
# Code generation
OBJS += \
diff --git a/src/backend/jit/llvm/SectionMemoryManager.LICENSE b/src/backend/jit/llvm/SectionMemoryManager.LICENSE
new file mode 100644
index 00000000000..fa6ac540007
--- /dev/null
+++ b/src/backend/jit/llvm/SectionMemoryManager.LICENSE
@@ -0,0 +1,279 @@
+==============================================================================
+The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
+==============================================================================
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+---- LLVM Exceptions to the Apache 2.0 License ----
+
+As an exception, if, as a result of your compiling your source code, portions
+of this Software are embedded into an Object form of such source code, you
+may redistribute such embedded portions in such Object form without complying
+with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
+
+In addition, if you combine or link compiled forms of this Software with
+software that is licensed under the GPLv2 ("Combined Software") and if a
+court of competent jurisdiction determines that the patent provision (Section
+3), the indemnity provision (Section 9) or other Section of the License
+conflicts with the conditions of the GPLv2, you may retroactively and
+prospectively choose to deem waived or otherwise exclude such Section(s) of
+the License, but only in their entirety and only with respect to the Combined
+Software.
+
+==============================================================================
+Software from third parties included in the LLVM Project:
+==============================================================================
+The LLVM Project contains third party software which is under different license
+terms. All such code will be identified clearly using at least one of two
+mechanisms:
+1) It will be in a separate directory tree with its own `LICENSE.txt` or
+ `LICENSE` file at the top containing the specific license and restrictions
+ which apply to that software, or
+2) It will contain specific license and restriction terms at the top of every
+ file.
+
+==============================================================================
+Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
+==============================================================================
+University of Illinois/NCSA
+Open Source License
+
+Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign.
+All rights reserved.
+
+Developed by:
+
+ LLVM Team
+
+ University of Illinois at Urbana-Champaign
+
+ http://llvm.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimers.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimers in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the names of the LLVM Team, University of Illinois at
+ Urbana-Champaign, nor the names of its contributors may be used to
+ endorse or promote products derived from this Software without specific
+ prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+
diff --git a/src/backend/jit/llvm/SectionMemoryManager.cpp b/src/backend/jit/llvm/SectionMemoryManager.cpp
new file mode 100644
index 00000000000..70a2dba10a9
--- /dev/null
+++ b/src/backend/jit/llvm/SectionMemoryManager.cpp
@@ -0,0 +1,412 @@
+/*
+ * This file is from https://github.com/llvm/llvm-project/pull/71968
+ * with minor modifications to avoid name clash and work with older
+ * LLVM versions. The llvm::backport::SectionMemoryManager class is a
+ * drop-in replacement for llvm::SectionMemoryManager, for use with
+ * llvm::RuntimeDyld. It fixes a memory layout bug on large memory
+ * ARM systems (see pull request for details). If the LLVM project
+ * eventually commits the change, we may need to resynchronize our
+ * copy with any further modifications, but they would be unlikely to
+ * backport it into the LLVM versions that we target so we would still
+ * need this copy.
+ *
+ * In the future we will switch to using JITLink instead of
+ * RuntimeDyld where possible, and later remove this code (.cpp, .h,
+ * .LICENSE) after all LLVM versions that we target allow it.
+ *
+ * This file is a modified copy of a part of the LLVM source code that
+ * we would normally access from the LLVM library. It is therefore
+ * covered by the license at https://llvm.org/LICENSE.txt, reproduced
+ * verbatim in SectionMemoryManager.LICENSE in fulfillment of clause
+ * 4a. The bugfix changes from the pull request are also covered, per
+ * clause 5.
+ */
+
+//===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the section-based memory manager used by the MCJIT
+// execution engine and RuntimeDyld
+//
+//===----------------------------------------------------------------------===//
+
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+
+#include "jit/SectionMemoryManager.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/Process.h"
+
+namespace llvm {
+namespace backport {
+
+bool SectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
+ uintptr_t Size) const {
+ for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= Size)
+ return true;
+ }
+ return false;
+}
+
+#if LLVM_VERSION_MAJOR < 16
+void SectionMemoryManager::reserveAllocationSpace(uintptr_t CodeSize,
+ uint32_t CodeAlign_i,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign_i,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign_i) {
+ Align CodeAlign(CodeAlign_i);
+ Align RODataAlign(RODataAlign_i);
+ Align RWDataAlign(RWDataAlign_i);
+#else
+void SectionMemoryManager::reserveAllocationSpace(
+ uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
+ Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
+#endif
+ if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
+ return;
+
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ // Code alignment needs to be at least the stub alignment - however, we
+ // don't have an easy way to get that here so as a workaround, we assume
+ // it's 8, which is the largest value I observed across all platforms.
+ constexpr uint64_t StubAlign = 8;
+ CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
+ RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
+ RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
+
+ // Get space required for each section. Use the same calculation as
+ // allocateSection because we need to be able to satisfy it.
+ uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
+ uint64_t RequiredRODataSize =
+ alignTo(RODataSize, RODataAlign) + RODataAlign.value();
+ uint64_t RequiredRWDataSize =
+ alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
+
+ if (hasSpace(CodeMem, RequiredCodeSize) &&
+ hasSpace(RODataMem, RequiredRODataSize) &&
+ hasSpace(RWDataMem, RequiredRWDataSize)) {
+ // Sufficient space in contiguous block already available.
+ return;
+ }
+
+ // MemoryManager does not have functions for releasing memory after it's
+ // allocated. Normally it tries to use any excess blocks that were allocated
+ // due to page alignment, but if we have insufficient free memory for the
+ // request this can lead to allocating disparate memory that can violate the
+ // ARM ABI. Clear free memory so only the new allocations are used, but do
+ // not release allocated memory as it may still be in-use.
+ CodeMem.FreeMem.clear();
+ RODataMem.FreeMem.clear();
+ RWDataMem.FreeMem.clear();
+
+ // Round up to the nearest page size. Blocks must be page-aligned.
+ RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
+ RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
+ RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
+ uint64_t RequiredSize =
+ RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
+
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ AllocationPurpose::RWData, RequiredSize, nullptr,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ return;
+ }
+ // CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
+ CodeMem.AllocatedMem.push_back(MB);
+ uintptr_t Addr = (uintptr_t)MB.base();
+ FreeMemBlock FreeMB;
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+
+ if (CodeSize > 0) {
+ assert(isAddrAligned(CodeAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
+ CodeMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredCodeSize;
+ }
+
+ if (RODataSize > 0) {
+ assert(isAddrAligned(RODataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
+ RODataMem.FreeMem.push_back(FreeMB);
+ Addr += RequiredRODataSize;
+ }
+
+ if (RWDataSize > 0) {
+ assert(isAddrAligned(RWDataAlign, (void *)Addr));
+ FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
+ RWDataMem.FreeMem.push_back(FreeMB);
+ }
+}
+
+uint8_t *SectionMemoryManager::allocateDataSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName,
+ bool IsReadOnly) {
+ if (IsReadOnly)
+ return allocateSection(SectionMemoryManager::AllocationPurpose::ROData,
+ Size, Alignment);
+ return allocateSection(SectionMemoryManager::AllocationPurpose::RWData, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateCodeSection(uintptr_t Size,
+ unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) {
+ return allocateSection(SectionMemoryManager::AllocationPurpose::Code, Size,
+ Alignment);
+}
+
+uint8_t *SectionMemoryManager::allocateSection(
+ SectionMemoryManager::AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment) {
+ if (!Alignment)
+ Alignment = 16;
+
+ assert(!(Alignment & (Alignment - 1)) && "Alignment must be a power of two.");
+
+ uintptr_t RequiredSize = Alignment * ((Size + Alignment - 1) / Alignment + 1);
+ uintptr_t Addr = 0;
+
+ MemoryGroup &MemGroup = [&]() -> MemoryGroup & {
+ switch (Purpose) {
+ case AllocationPurpose::Code:
+ return CodeMem;
+ case AllocationPurpose::ROData:
+ return RODataMem;
+ case AllocationPurpose::RWData:
+ return RWDataMem;
+ }
+ llvm_unreachable("Unknown SectionMemoryManager::AllocationPurpose");
+ }();
+
+ // Look in the list of free memory regions and use a block there if one
+ // is available.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ if (FreeMB.Free.allocatedSize() >= RequiredSize) {
+ Addr = (uintptr_t)FreeMB.Free.base();
+ uintptr_t EndOfBlock = Addr + FreeMB.Free.allocatedSize();
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ if (FreeMB.PendingPrefixIndex == (unsigned)-1) {
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // Remember this pending block, such that future allocations can just
+ // modify it rather than creating a new one
+ FreeMB.PendingPrefixIndex = MemGroup.PendingMem.size() - 1;
+ } else {
+ sys::MemoryBlock &PendingMB =
+ MemGroup.PendingMem[FreeMB.PendingPrefixIndex];
+ PendingMB = sys::MemoryBlock(PendingMB.base(),
+ Addr + Size - (uintptr_t)PendingMB.base());
+ }
+
+ // Remember how much free space is now left in this block
+ FreeMB.Free =
+ sys::MemoryBlock((void *)(Addr + Size), EndOfBlock - Addr - Size);
+ return (uint8_t *)Addr;
+ }
+ }
+
+ // No pre-allocated free block was large enough. Allocate a new memory region.
+ // Note that all sections get allocated as read-write. The permissions will
+ // be updated later based on memory group.
+ //
+ // FIXME: It would be useful to define a default allocation size (or add
+ // it as a constructor parameter) to minimize the number of allocations.
+ //
+ // FIXME: Initialize the Near member for each memory group to avoid
+ // interleaving.
+ std::error_code ec;
+ sys::MemoryBlock MB = MMapper->allocateMappedMemory(
+ Purpose, RequiredSize, &MemGroup.Near,
+ sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
+ if (ec) {
+ // FIXME: Add error propagation to the interface.
+ return nullptr;
+ }
+
+ // Save this address as the basis for our next request
+ MemGroup.Near = MB;
+
+ // Copy the address to all the other groups, if they have not
+ // been initialized.
+ if (CodeMem.Near.base() == nullptr)
+ CodeMem.Near = MB;
+ if (RODataMem.Near.base() == nullptr)
+ RODataMem.Near = MB;
+ if (RWDataMem.Near.base() == nullptr)
+ RWDataMem.Near = MB;
+
+ // Remember that we allocated this memory
+ MemGroup.AllocatedMem.push_back(MB);
+ Addr = (uintptr_t)MB.base();
+ uintptr_t EndOfBlock = Addr + MB.allocatedSize();
+
+ // Align the address.
+ Addr = (Addr + Alignment - 1) & ~(uintptr_t)(Alignment - 1);
+
+ // The part of the block we're giving out to the user is now pending
+ MemGroup.PendingMem.push_back(sys::MemoryBlock((void *)Addr, Size));
+
+ // The allocateMappedMemory may allocate much more memory than we need. In
+ // this case, we store the unused memory as a free memory block.
+ unsigned FreeSize = EndOfBlock - Addr - Size;
+ if (FreeSize > 16) {
+ FreeMemBlock FreeMB;
+ FreeMB.Free = sys::MemoryBlock((void *)(Addr + Size), FreeSize);
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ MemGroup.FreeMem.push_back(FreeMB);
+ }
+
+ // Return aligned address
+ return (uint8_t *)Addr;
+}
+
+bool SectionMemoryManager::finalizeMemory(std::string *ErrMsg) {
+ // FIXME: Should in-progress permissions be reverted if an error occurs?
+ std::error_code ec;
+
+ // Make code memory executable.
+ ec = applyMemoryGroupPermissions(CodeMem,
+ sys::Memory::MF_READ | sys::Memory::MF_EXEC);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Make read-only data memory read-only.
+ ec = applyMemoryGroupPermissions(RODataMem, sys::Memory::MF_READ);
+ if (ec) {
+ if (ErrMsg) {
+ *ErrMsg = ec.message();
+ }
+ return true;
+ }
+
+ // Read-write data memory already has the correct permissions
+
+ // Some platforms with separate data cache and instruction cache require
+ // explicit cache flush, otherwise JIT code manipulations (like resolved
+ // relocations) will get to the data cache but not to the instruction cache.
+ invalidateInstructionCache();
+
+ return false;
+}
+
+static sys::MemoryBlock trimBlockToPageSize(sys::MemoryBlock M) {
+ static const size_t PageSize = sys::Process::getPageSizeEstimate();
+
+ size_t StartOverlap =
+ (PageSize - ((uintptr_t)M.base() % PageSize)) % PageSize;
+
+ size_t TrimmedSize = M.allocatedSize();
+ TrimmedSize -= StartOverlap;
+ TrimmedSize -= TrimmedSize % PageSize;
+
+ sys::MemoryBlock Trimmed((void *)((uintptr_t)M.base() + StartOverlap),
+ TrimmedSize);
+
+ assert(((uintptr_t)Trimmed.base() % PageSize) == 0);
+ assert((Trimmed.allocatedSize() % PageSize) == 0);
+ assert(M.base() <= Trimmed.base() &&
+ Trimmed.allocatedSize() <= M.allocatedSize());
+
+ return Trimmed;
+}
+
+std::error_code
+SectionMemoryManager::applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions) {
+ for (sys::MemoryBlock &MB : MemGroup.PendingMem)
+ if (std::error_code EC = MMapper->protectMappedMemory(MB, Permissions))
+ return EC;
+
+ MemGroup.PendingMem.clear();
+
+ // Now go through free blocks and trim any of them that don't span the entire
+ // page because one of the pending blocks may have overlapped it.
+ for (FreeMemBlock &FreeMB : MemGroup.FreeMem) {
+ FreeMB.Free = trimBlockToPageSize(FreeMB.Free);
+ // We cleared the PendingMem list, so all these pointers are now invalid
+ FreeMB.PendingPrefixIndex = (unsigned)-1;
+ }
+
+ // Remove all blocks which are now empty
+ erase_if(MemGroup.FreeMem, [](FreeMemBlock &FreeMB) {
+ return FreeMB.Free.allocatedSize() == 0;
+ });
+
+ return std::error_code();
+}
+
+void SectionMemoryManager::invalidateInstructionCache() {
+ for (sys::MemoryBlock &Block : CodeMem.PendingMem)
+ sys::Memory::InvalidateInstructionCache(Block.base(),
+ Block.allocatedSize());
+}
+
+SectionMemoryManager::~SectionMemoryManager() {
+ for (MemoryGroup *Group : {&CodeMem, &RWDataMem, &RODataMem}) {
+ for (sys::MemoryBlock &Block : Group->AllocatedMem)
+ MMapper->releaseMappedMemory(Block);
+ }
+}
+
+SectionMemoryManager::MemoryMapper::~MemoryMapper() = default;
+
+void SectionMemoryManager::anchor() {}
+
+namespace {
+// Trivial implementation of SectionMemoryManager::MemoryMapper that just calls
+// into sys::Memory.
+class DefaultMMapper final : public SectionMemoryManager::MemoryMapper {
+public:
+ sys::MemoryBlock
+ allocateMappedMemory(SectionMemoryManager::AllocationPurpose Purpose,
+ size_t NumBytes, const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) override {
+ return sys::Memory::allocateMappedMemory(NumBytes, NearBlock, Flags, EC);
+ }
+
+ std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) override {
+ return sys::Memory::protectMappedMemory(Block, Flags);
+ }
+
+ std::error_code releaseMappedMemory(sys::MemoryBlock &M) override {
+ return sys::Memory::releaseMappedMemory(M);
+ }
+};
+} // namespace
+
+SectionMemoryManager::SectionMemoryManager(MemoryMapper *UnownedMM,
+ bool ReserveAlloc)
+ : MMapper(UnownedMM), OwnedMMapper(nullptr),
+ ReserveAllocation(ReserveAlloc) {
+ if (!MMapper) {
+ OwnedMMapper = std::make_unique<DefaultMMapper>();
+ MMapper = OwnedMMapper.get();
+ }
+}
+
+} // namespace backport
+} // namespace llvm
+
+#endif
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index f346af9c755..72411d0f805 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -34,6 +34,7 @@
#endif
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
#include "jit/llvmjit_emit.h"
#include "miscadmin.h"
#include "portability/instr_time.h"
@@ -1166,8 +1167,14 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error)
static LLVMOrcObjectLayerRef
llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
{
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+ LLVMOrcObjectLayerRef objlayer =
+ LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
+#else
LLVMOrcObjectLayerRef objlayer =
LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManager(ES);
+#endif
+
#if defined(HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER) && HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
if (jit_debugging_support)
diff --git a/src/backend/jit/llvm/llvmjit_wrap.cpp b/src/backend/jit/llvm/llvmjit_wrap.cpp
index 7f7623dac64..9e8d63f0122 100644
--- a/src/backend/jit/llvm/llvmjit_wrap.cpp
+++ b/src/backend/jit/llvm/llvmjit_wrap.cpp
@@ -20,6 +20,14 @@ extern "C"
#include <llvm/IR/Function.h>
#include "jit/llvmjit.h"
+#include "jit/llvmjit_backport.h"
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
+#include "jit/SectionMemoryManager.h"
+#include <llvm/Support/CBindingWrapping.h>
+#endif
/*
@@ -37,3 +45,15 @@ LLVMGetFunctionType(LLVMValueRef r)
{
return llvm::wrap(llvm::unwrap<llvm::Function>(r)->getFunctionType());
}
+
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ExecutionSession, LLVMOrcExecutionSessionRef)
+DEFINE_SIMPLE_CONVERSION_FUNCTIONS(llvm::orc::ObjectLayer, LLVMOrcObjectLayerRef);
+
+LLVMOrcObjectLayerRef
+LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES)
+{
+ return wrap(new llvm::orc::RTDyldObjectLinkingLayer(
+ *unwrap(ES), [] { return std::make_unique<llvm::backport::SectionMemoryManager>(nullptr, true); }));
+}
+#endif
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index c1b6a9363eb..ce49043f1d0 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -14,6 +14,7 @@ llvmjit_sources += files(
'llvmjit_error.cpp',
'llvmjit_inline.cpp',
'llvmjit_wrap.cpp',
+ 'SectionMemoryManager.cpp',
)
# Code generation
diff --git a/src/include/jit/SectionMemoryManager.h b/src/include/jit/SectionMemoryManager.h
new file mode 100644
index 00000000000..93cf9771570
--- /dev/null
+++ b/src/include/jit/SectionMemoryManager.h
@@ -0,0 +1,226 @@
+/*
+ * This is a copy LLVM source code modified by the PostgreSQL project.
+ * See SectionMemoryManager.cpp for notes on provenance and license.
+ */
+
+//===- SectionMemoryManager.h - Memory manager for MCJIT/RtDyld -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of a section-based memory manager used by
+// the MCJIT execution engine and RuntimeDyld.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+#define LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Memory.h"
+#include <cstdint>
+#include <string>
+#include <system_error>
+
+namespace llvm {
+namespace backport {
+
+/// This is a simple memory manager which implements the methods called by
+/// the RuntimeDyld class to allocate memory for section-based loading of
+/// objects, usually those generated by the MCJIT execution engine.
+///
+/// This memory manager allocates all section memory as read-write. The
+/// RuntimeDyld will copy JITed section memory into these allocated blocks
+/// and perform any necessary linking and relocations.
+///
+/// Any client using this memory manager MUST ensure that section-specific
+/// page permissions have been applied before attempting to execute functions
+/// in the JITed object. Permissions can be applied either by calling
+/// MCJIT::finalizeObject or by calling SectionMemoryManager::finalizeMemory
+/// directly. Clients of MCJIT should call MCJIT::finalizeObject.
+class SectionMemoryManager : public RTDyldMemoryManager {
+public:
+ /// This enum describes the various reasons to allocate pages from
+ /// allocateMappedMemory.
+ enum class AllocationPurpose {
+ Code,
+ ROData,
+ RWData,
+ };
+
+ /// Implementations of this interface are used by SectionMemoryManager to
+ /// request pages from the operating system.
+ class MemoryMapper {
+ public:
+ /// This method attempts to allocate \p NumBytes bytes of virtual memory for
+ /// \p Purpose. \p NearBlock may point to an existing allocation, in which
+ /// case an attempt is made to allocate more memory near the existing block.
+ /// The actual allocated address is not guaranteed to be near the requested
+ /// address. \p Flags is used to set the initial protection flags for the
+ /// block of the memory. \p EC [out] returns an object describing any error
+ /// that occurs.
+ ///
+ /// This method may allocate more than the number of bytes requested. The
+ /// actual number of bytes allocated is indicated in the returned
+ /// MemoryBlock.
+ ///
+ /// The start of the allocated block must be aligned with the system
+ /// allocation granularity (64K on Windows, page size on Linux). If the
+ /// address following \p NearBlock is not so aligned, it will be rounded up
+ /// to the next allocation granularity boundary.
+ ///
+ /// \r a non-null MemoryBlock if the function was successful, otherwise a
+ /// null MemoryBlock with \p EC describing the error.
+ virtual sys::MemoryBlock
+ allocateMappedMemory(AllocationPurpose Purpose, size_t NumBytes,
+ const sys::MemoryBlock *const NearBlock,
+ unsigned Flags, std::error_code &EC) = 0;
+
+ /// This method sets the protection flags for a block of memory to the state
+ /// specified by \p Flags. The behavior is not specified if the memory was
+ /// not allocated using the allocateMappedMemory method.
+ /// \p Block describes the memory block to be protected.
+ /// \p Flags specifies the new protection state to be assigned to the block.
+ ///
+ /// If \p Flags is MF_WRITE, the actual behavior varies with the operating
+ /// system (i.e. MF_READ | MF_WRITE on Windows) and the target architecture
+ /// (i.e. MF_WRITE -> MF_READ | MF_WRITE on i386).
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code protectMappedMemory(const sys::MemoryBlock &Block,
+ unsigned Flags) = 0;
+
+ /// This method releases a block of memory that was allocated with the
+ /// allocateMappedMemory method. It should not be used to release any memory
+ /// block allocated any other way.
+ /// \p Block describes the memory to be released.
+ ///
+ /// \r error_success if the function was successful, or an error_code
+ /// describing the failure if an error occurred.
+ virtual std::error_code releaseMappedMemory(sys::MemoryBlock &M) = 0;
+
+ virtual ~MemoryMapper();
+ };
+
+ /// Creates a SectionMemoryManager instance with \p MM as the associated
+ /// memory mapper. If \p MM is nullptr then a default memory mapper is used
+ /// that directly calls into the operating system.
+ ///
+ /// If \p ReserveAlloc is true all memory will be pre-allocated, and any
+ /// attempts to allocate beyond pre-allocated memory will fail.
+ SectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
+ SectionMemoryManager(const SectionMemoryManager &) = delete;
+ void operator=(const SectionMemoryManager &) = delete;
+ ~SectionMemoryManager() override;
+
+ /// Enable reserveAllocationSpace when requested.
+ bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
+
+ /// Implements allocating all memory in a single block. This is required to
+ /// limit memory offsets to fit the ARM ABI; large memory systems may
+ /// otherwise allocate separate sections too far apart.
+#if LLVM_VERSION_MAJOR < 16
+ virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
+ uintptr_t RODataSize,
+ uint32_t RODataAlign,
+ uintptr_t RWDataSize,
+ uint32_t RWDataAlign) override;
+#else
+ void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
+ uintptr_t RODataSize, Align RODataAlign,
+ uintptr_t RWDataSize, Align RWDataAlign) override;
+#endif
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID,
+ StringRef SectionName) override;
+
+ /// Allocates a memory block of (at least) the given size suitable for
+ /// executable code.
+ ///
+ /// The value of \p Alignment must be a power of two. If \p Alignment is zero
+ /// a default alignment of 16 will be used.
+ uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+ unsigned SectionID, StringRef SectionName,
+ bool isReadOnly) override;
+
+ /// Update section-specific memory permissions and other attributes.
+ ///
+ /// This method is called when object loading is complete and section page
+ /// permissions can be applied. It is up to the memory manager implementation
+ /// to decide whether or not to act on this method. The memory manager will
+ /// typically allocate all sections as read-write and then apply specific
+ /// permissions when this method is called. Code sections cannot be executed
+ /// until this function has been called. In addition, any cache coherency
+ /// operations needed to reliably use the memory are also performed.
+ ///
+ /// \returns true if an error occurred, false otherwise.
+ bool finalizeMemory(std::string *ErrMsg = nullptr) override;
+
+ /// Invalidate instruction cache for code sections.
+ ///
+ /// Some platforms with separate data cache and instruction cache require
+ /// explicit cache flush, otherwise JIT code manipulations (like resolved
+ /// relocations) will get to the data cache but not to the instruction cache.
+ ///
+ /// This method is called from finalizeMemory.
+ virtual void invalidateInstructionCache();
+
+private:
+ struct FreeMemBlock {
+ // The actual block of free memory
+ sys::MemoryBlock Free;
+ // If there is a pending allocation from the same reservation right before
+ // this block, store it's index in PendingMem, to be able to update the
+ // pending region if part of this block is allocated, rather than having to
+ // create a new one
+ unsigned PendingPrefixIndex;
+ };
+
+ struct MemoryGroup {
+ // PendingMem contains all blocks of memory (subblocks of AllocatedMem)
+ // which have not yet had their permissions applied, but have been given
+ // out to the user. FreeMem contains all block of memory, which have
+ // neither had their permissions applied, nor been given out to the user.
+ SmallVector<sys::MemoryBlock, 16> PendingMem;
+ SmallVector<FreeMemBlock, 16> FreeMem;
+
+ // All memory blocks that have been requested from the system
+ SmallVector<sys::MemoryBlock, 16> AllocatedMem;
+
+ sys::MemoryBlock Near;
+ };
+
+ uint8_t *allocateSection(AllocationPurpose Purpose, uintptr_t Size,
+ unsigned Alignment);
+
+ std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
+ unsigned Permissions);
+
+ bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
+
+ void anchor() override;
+
+ MemoryGroup CodeMem;
+ MemoryGroup RWDataMem;
+ MemoryGroup RODataMem;
+ MemoryMapper *MMapper;
+ std::unique_ptr<MemoryMapper> OwnedMMapper;
+ bool ReserveAllocation;
+};
+
+} // end namespace backport
+} // end namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_BACKPORT_SECTIONMEMORYMANAGER_H
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 420775b1899..dc0d86f4c1d 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -17,7 +17,12 @@
*/
#ifdef USE_LLVM
+#include "jit/llvmjit_backport.h"
+
#include <llvm-c/Types.h>
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#include <llvm-c/OrcEE.h>
+#endif
/*
@@ -135,6 +140,9 @@ extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDes
*/
extern LLVMTypeRef LLVMGetFunctionReturnType(LLVMValueRef r);
extern LLVMTypeRef LLVMGetFunctionType(LLVMValueRef r);
+#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+extern LLVMOrcObjectLayerRef LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(LLVMOrcExecutionSessionRef ES);
+#endif
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/include/jit/llvmjit_backport.h b/src/include/jit/llvmjit_backport.h
new file mode 100644
index 00000000000..92874f7998c
--- /dev/null
+++ b/src/include/jit/llvmjit_backport.h
@@ -0,0 +1,25 @@
+/*
+ * A small header than can be included by backported LLVM code or PostgreSQL
+ * code, to control conditional compilation.
+ */
+#ifndef LLVMJIT_BACKPORT_H
+#define LLVMJIT_BACKPORT_H
+
+#include <llvm/Config/llvm-config.h>
+
+/*
+ * LLVM's RuntimeDyld can produce code that crashes on larger memory ARM
+ * systems, because llvm::SectionMemoryManager allocates multiple pieces of
+ * memory that can be placed too far apart for the generated code. See
+ * src/backend/jit/llvm/SectionMemoryManager.cpp for the patched replacement
+ * class llvm::backport::SectionMemoryManager that we use as a workaround.
+ * This header controls whether we use it.
+ *
+ * We have adjusted it to compile against a range of LLVM versions, but not
+ * further back than 12 for now.
+ */
+#if defined(__aarch64__) && LLVM_VERSION_MAJOR > 11
+#define USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#endif
+
+#endif
diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck
index 3fc737d2cc1..fb91b5760c4 100755
--- a/src/tools/pginclude/headerscheck
+++ b/src/tools/pginclude/headerscheck
@@ -168,6 +168,9 @@ do
# This produces a "no previous prototype" warning.
! $cplusplus && test "$f" = src/include/storage/checksum_impl.h && continue
+ # SectionMemoryManager.h is C++
+ test "$f" = src/include/jit/SectionMemoryManager.h && continue
+
# ppport.h is not under our control, so we can't make it standalone.
test "$f" = src/pl/plperl/ppport.h && continue
diff --git a/src/tools/pgindent/exclude_file_patterns b/src/tools/pgindent/exclude_file_patterns
index 99e57f93bbc..68aae9b97a4 100644
--- a/src/tools/pgindent/exclude_file_patterns
+++ b/src/tools/pgindent/exclude_file_patterns
@@ -4,8 +4,9 @@
src/include/storage/s_lock\.h$
src/include/port/atomics/
#
-# This contains C++ constructs that confuse pgindent.
+# These contains C++ constructs that confuse pgindent.
src/include/jit/llvmjit\.h$
+src/include/jit/SectionMemoryManager\.h$
#
# These are generated files with incomplete code fragments that
# confuse pgindent.
--
2.46.2
On Tue, Nov 5, 2024 at 9:00 AM Thomas Munro <thomas.munro@gmail.com> wrote:
Reasoning: Old LLVM required C++11. LLVM 9 switched to C++14. LLVM
14 switched C++17. Pretty soon they'll flip to C++20 or C++23, they
don't mess around. The corresponding -std=c++XX flag finishes up in
our compile lines, because llvm-config --cxxflags spits it out, to
match the features they're using in headers that we include (easy to
spot examples being std::make_unique (C++14) and std::string_view
(C++17)), so you might say that PostgreSQL indirectly chases C++
standards much faster than it chases C standards. This particular
code is a special case because it's guarded for LLVM 12+ only, so it's
OK to use C++14 in that limited context even in back branches. We
have to be careful that it doesn't contain C++17 code since it came
from recent LLVM, but it doesn't seem to by inspection, and you can
check on a machine with CXX=g++ and LLVM 14 on Linux, which uses
-std=c++14 and fails if you add a use of <string_view> and
std::string_view. (Warning: the system C++ standard library on Macs
and other Clang-based systems doesn't have enough version guards so it
won't complain, but GCC and its standard library will explicitly tell
you not to use C++17 features in a C++14 program.)
I think the switch to C++14 happened with LLVM 10[0]https://github.com/llvm/llvm-project/blob/release/10.x/llvm/CMakeLists.txt#L53 while the C++17
switch happened with LLVM 16[1]https://github.com/llvm/llvm-project/blob/release/16.x/llvm/CMakeLists.txt#L70. Double checking on an ubuntu jammy
(can't install earlier llvm version than 12 on those):
VERSIONS=(20 19 18 17 16 15 14 13 12)
for version in ${VERSIONS[@]}; do
llvm-config-$version --cxxflags
done
-I/usr/lib/llvm-20/include -std=c++17 -fno-exceptions
-funwind-tables -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS
-D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-19/include -std=c++17 -fno-exceptions
-funwind-tables -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS
-D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-18/include -std=c++17 -fno-exceptions
-funwind-tables -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS
-D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-17/include -std=c++17 -fno-exceptions
-funwind-tables -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS
-D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-16/include -std=c++17 -fno-exceptions -D_GNU_SOURCE
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-15/include -std=c++14 -fno-exceptions -D_GNU_SOURCE
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-14/include -std=c++14 -fno-exceptions -D_GNU_SOURCE
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-13/include -std=c++14 -fno-exceptions -D_GNU_SOURCE
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-I/usr/lib/llvm-12/include -std=c++14 -fno-exceptions -D_GNU_SOURCE
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
Which is still fine since, as you said, the code only applied for
LLVM12+ which will always have at least c++14. I've tried compiling
and running against all those llvm versions and the associated stdc++
version earlier in the thread[2]/messages/by-id/CAO6_XqqxEQ=JY+tYO-KQn3_pKQ3O-mPojcwG54L5eptiu1cSJQ@mail.gmail.com and had no issues.
If there are no further comments, I'm going to push this to all
branches tomorrow morning. For master only, I will remove the #if
condition and comment about LLVM 12+, as we now require 14+.
Patch looks good to me.
[0]: https://github.com/llvm/llvm-project/blob/release/10.x/llvm/CMakeLists.txt#L53
[1]: https://github.com/llvm/llvm-project/blob/release/16.x/llvm/CMakeLists.txt#L70
[2]: /messages/by-id/CAO6_XqqxEQ=JY+tYO-KQn3_pKQ3O-mPojcwG54L5eptiu1cSJQ@mail.gmail.com