This is an automated email from the ASF dual-hosted git repository.
HuaHuaY pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new bcaac3ba3f4 GH-51245: [C++][Compute][Gandiva] Add support for LLVM
23.1 (#51266)
bcaac3ba3f4 is described below
commit bcaac3ba3f45a9ff597f6f3eb14adf8fe7d34bfc
Author: Zehua Zou <[email protected]>
AuthorDate: Mon Sep 21 18:16:03 2026 +0800
GH-51245: [C++][Compute][Gandiva] Add support for LLVM 23.1 (#51266)
### Rationale for this change
Add support for LLVM 23.1.
### What changes are included in this PR?
1. Remove `static` from some template functions in header files.
2. Fixed a bug in `CastImpl` template instantiation about `Date` and
`Timestamp` data types, and add a unit test.
3. Remove the build target attributes in bitcode generated by Gandiva.
4. Add `zeroext` attr when LLVM JIT passes `i1` to GCC-compiled functions.
### Are these changes tested?
Yes.
### Are there any user-facing changes?
Yes.
* GitHub Issue: #51245
Lead-authored-by: Zehua Zou <[email protected]>
Co-authored-by: Copilot Autofix powered by AI
<[email protected]>
Signed-off-by: Zehua Zou <[email protected]>
---
ci/conda_env_gandiva.txt | 2 +-
cpp/CMakeLists.txt | 1 +
cpp/src/arrow/compute/exec.cc | 10 ---
cpp/src/arrow/compute/function_internal.h | 4 +-
cpp/src/arrow/compute/kernels/codegen_internal.h | 14 ++--
cpp/src/arrow/compute/kernels/vector_hash.cc | 2 +-
cpp/src/arrow/scalar.cc | 20 ++++--
cpp/src/arrow/scalar_test.cc | 45 +++++++++++++
cpp/src/arrow/util/async_generator.h | 7 +-
cpp/src/arrow/util/bit_block_counter.h | 24 +++----
cpp/src/arrow/util/future.h | 2 +-
cpp/src/gandiva/decimal_ir.cc | 14 ++--
cpp/src/gandiva/engine.cc | 63 +++++++++++++-----
cpp/src/gandiva/llvm_generator.cc | 12 ++--
cpp/src/gandiva/llvm_generator.h | 1 +
cpp/src/gandiva/llvm_generator_test.cc | 57 ++++++++++++++++
cpp/src/gandiva/llvm_util_internal.h | 84 ++++++++++++++++++++++++
17 files changed, 291 insertions(+), 71 deletions(-)
diff --git a/ci/conda_env_gandiva.txt b/ci/conda_env_gandiva.txt
index 7127dc90558..2eeedbc94ef 100644
--- a/ci/conda_env_gandiva.txt
+++ b/ci/conda_env_gandiva.txt
@@ -16,4 +16,4 @@
# under the License.
clang
-llvmdev<23
+llvmdev<24
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 8fd38974ec6..d39120dec2e 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -179,6 +179,7 @@ set(ARROW_DOC_DIR "${CMAKE_INSTALL_DOCDIR}")
set(BUILD_SUPPORT_DIR "${CMAKE_SOURCE_DIR}/build-support")
set(ARROW_LLVM_VERSIONS
+ "23.1"
"22.1"
"21.1"
"20.1"
diff --git a/cpp/src/arrow/compute/exec.cc b/cpp/src/arrow/compute/exec.cc
index 411ff0bb026..db86a75c53f 100644
--- a/cpp/src/arrow/compute/exec.cc
+++ b/cpp/src/arrow/compute/exec.cc
@@ -1185,16 +1185,6 @@ class ScalarAggExecutor : public
KernelExecutorImpl<ScalarAggregateKernel> {
const FunctionOptions* options_;
};
-template <typename ExecutorType,
- typename FunctionType = typename ExecutorType::FunctionType>
-Result<std::unique_ptr<KernelExecutor>> MakeExecutor(ExecContext* ctx,
- const Function* func,
- const FunctionOptions*
options) {
- DCHECK_EQ(ExecutorType::function_kind, func->kind());
- auto typed_func = checked_cast<const FunctionType*>(func);
- return std::make_unique<ExecutorType>(ctx, typed_func, options);
-}
-
} // namespace
Status PropagateNulls(KernelContext* ctx, const ExecSpan& batch, ArrayData*
output) {
diff --git a/cpp/src/arrow/compute/function_internal.h
b/cpp/src/arrow/compute/function_internal.h
index d1f8d06de84..92f8529ab78 100644
--- a/cpp/src/arrow/compute/function_internal.h
+++ b/cpp/src/arrow/compute/function_internal.h
@@ -535,8 +535,8 @@ static inline std::enable_if_t<is_optional_v<T>, Result<T>>
GenericFromScalar(
}
template <typename T>
-static enable_if_same<typename CTypeTraits<T>::ArrowType, ListType, Result<T>>
-GenericFromScalar(const std::shared_ptr<Scalar>& value) {
+enable_if_same<typename CTypeTraits<T>::ArrowType, ListType, Result<T>>
GenericFromScalar(
+ const std::shared_ptr<Scalar>& value) {
using ValueType = typename T::value_type;
if (value->type->id() != Type::LIST) {
return Status::Invalid("Expected type LIST but got ",
value->type->ToString());
diff --git a/cpp/src/arrow/compute/kernels/codegen_internal.h
b/cpp/src/arrow/compute/kernels/codegen_internal.h
index bfc529dc0a4..3a2bcefab0a 100644
--- a/cpp/src/arrow/compute/kernels/codegen_internal.h
+++ b/cpp/src/arrow/compute/kernels/codegen_internal.h
@@ -481,8 +481,8 @@ struct UnboxScalar<Decimal256Type> {
template <typename T, typename VisitFunc, typename NullFunc>
requires std::is_void_v<std::invoke_result_t<VisitFunc, typename
GetViewType<T>::T>>
-static void VisitArrayValuesInline(const ArraySpan& arr, VisitFunc&&
valid_func,
- NullFunc&& null_func) {
+void VisitArrayValuesInline(const ArraySpan& arr, VisitFunc&& valid_func,
+ NullFunc&& null_func) {
VisitArraySpanInline<T>(
arr,
[&](typename GetViewType<T>::PhysicalType v) {
@@ -494,8 +494,8 @@ static void VisitArrayValuesInline(const ArraySpan& arr,
VisitFunc&& valid_func,
template <typename T, typename VisitFunc, typename NullFunc>
requires std::is_same_v<std::invoke_result_t<VisitFunc, typename
GetViewType<T>::T>,
Status>
-static Status VisitArrayValuesInline(const ArraySpan& arr, VisitFunc&&
valid_func,
- NullFunc&& null_func) {
+Status VisitArrayValuesInline(const ArraySpan& arr, VisitFunc&& valid_func,
+ NullFunc&& null_func) {
return VisitArraySpanInline<T>(
arr,
[&](typename GetViewType<T>::PhysicalType v) {
@@ -507,8 +507,8 @@ static Status VisitArrayValuesInline(const ArraySpan& arr,
VisitFunc&& valid_fun
// Like VisitArrayValuesInline, but for binary functions.
template <typename Arg0Type, typename Arg1Type, typename VisitFunc, typename
NullFunc>
-static void VisitTwoArrayValuesInline(const ArraySpan& arr0, const ArraySpan&
arr1,
- VisitFunc&& valid_func, NullFunc&&
null_func) {
+void VisitTwoArrayValuesInline(const ArraySpan& arr0, const ArraySpan& arr1,
+ VisitFunc&& valid_func, NullFunc&& null_func) {
ArrayIterator<Arg0Type> arr0_it(arr0);
ArrayIterator<Arg1Type> arr1_it(arr1);
@@ -584,7 +584,7 @@ namespace applicator {
// static Status Call(KernelContext*, const Scalar& arg0, const ArraySpan&
arg1,
// ExecResult* out)
template <typename Operator>
-static Status SimpleBinary(KernelContext* ctx, const ExecSpan& batch,
ExecResult* out) {
+Status SimpleBinary(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
if (batch.length == 0) return Status::OK();
if (batch[0].is_array()) {
diff --git a/cpp/src/arrow/compute/kernels/vector_hash.cc
b/cpp/src/arrow/compute/kernels/vector_hash.cc
index f42972e18af..9a29dcf333f 100644
--- a/cpp/src/arrow/compute/kernels/vector_hash.cc
+++ b/cpp/src/arrow/compute/kernels/vector_hash.cc
@@ -136,7 +136,7 @@ class ValueCountsAction final : ActionBase {
}
template <class Index>
- void ObserveNullNotFound(Index index) {
+ [[maybe_unused]] void ObserveNullNotFound(Index index) {
ARROW_LOG(FATAL) << "ObserveNullNotFound without err_status should not be
called";
}
diff --git a/cpp/src/arrow/scalar.cc b/cpp/src/arrow/scalar.cc
index 22cffae05cb..e619c5344d3 100644
--- a/cpp/src/arrow/scalar.cc
+++ b/cpp/src/arrow/scalar.cc
@@ -17,6 +17,7 @@
#include "arrow/scalar.h"
+#include <chrono>
#include <memory>
#include <ostream>
#include <sstream>
@@ -31,6 +32,7 @@
#include "arrow/type.h"
#include "arrow/util/bitmap_ops.h"
#include "arrow/util/checked_cast.h"
+#include "arrow/util/chrono_internal.h"
#include "arrow/util/decimal.h"
#include "arrow/util/formatting.h"
#include "arrow/util/hashing.h"
@@ -1191,13 +1193,13 @@ constexpr int64_t kMillisecondsInDay = 86400000;
// date to date
template <typename To>
-enable_if_t<std::is_same<To, Date64Scalar>::value,
Result<std::shared_ptr<Scalar>>>
+enable_if_t<std::is_same<To, Date64Type>::value,
Result<std::shared_ptr<Scalar>>>
CastImpl(const Date32Scalar& from, std::shared_ptr<DataType> to_type) {
return std::make_shared<Date64Scalar>(from.value * kMillisecondsInDay,
std::move(to_type));
}
template <typename To>
-enable_if_t<std::is_same<To, Date32Scalar>::value,
Result<std::shared_ptr<Scalar>>>
+enable_if_t<std::is_same<To, Date32Type>::value,
Result<std::shared_ptr<Scalar>>>
CastImpl(const Date64Scalar& from, std::shared_ptr<DataType> to_type) {
return std::make_shared<Date32Scalar>(
static_cast<int32_t>(from.value / kMillisecondsInDay),
std::move(to_type));
@@ -1205,27 +1207,31 @@ CastImpl(const Date64Scalar& from,
std::shared_ptr<DataType> to_type) {
// timestamp to date
template <typename To>
-enable_if_t<std::is_same<To, Date64Scalar>::value,
Result<std::shared_ptr<Scalar>>>
+enable_if_t<std::is_same<To, Date64Type>::value,
Result<std::shared_ptr<Scalar>>>
CastImpl(const TimestampScalar& from, std::shared_ptr<DataType> to_type) {
ARROW_ASSIGN_OR_RAISE(
auto millis,
util::ConvertTimestampValue(from.type, timestamp(TimeUnit::MILLI),
from.value));
- return std::make_shared<Date64Scalar>(millis - millis % kMillisecondsInDay,
+ const auto days_since_epoch =
+
internal::chrono::floor<internal::chrono::days>(std::chrono::milliseconds{millis});
+ return std::make_shared<Date64Scalar>(days_since_epoch.count() *
kMillisecondsInDay,
std::move(to_type));
}
template <typename To>
-enable_if_t<std::is_same<To, Date32Scalar>::value,
Result<std::shared_ptr<Scalar>>>
+enable_if_t<std::is_same<To, Date32Type>::value,
Result<std::shared_ptr<Scalar>>>
CastImpl(const TimestampScalar& from, std::shared_ptr<DataType> to_type) {
ARROW_ASSIGN_OR_RAISE(
auto millis,
util::ConvertTimestampValue(from.type, timestamp(TimeUnit::MILLI),
from.value));
- return std::make_shared<Date32Scalar>(static_cast<int32_t>(millis /
kMillisecondsInDay),
+ const auto days_since_epoch =
+
internal::chrono::floor<internal::chrono::days>(std::chrono::milliseconds{millis});
+ return
std::make_shared<Date32Scalar>(static_cast<int32_t>(days_since_epoch.count()),
std::move(to_type));
}
// date to timestamp
template <typename To, typename From>
-enable_if_timestamp<Result<std::shared_ptr<To>>> CastImpl(
+enable_if_timestamp<To, Result<std::shared_ptr<Scalar>>> CastImpl(
const DateScalar<From>& from, std::shared_ptr<DataType> to_type) {
using ToScalar = typename TypeTraits<To>::ScalarType;
int64_t millis = from.value;
diff --git a/cpp/src/arrow/scalar_test.cc b/cpp/src/arrow/scalar_test.cc
index a08ea6aa96d..9594e8883b3 100644
--- a/cpp/src/arrow/scalar_test.cc
+++ b/cpp/src/arrow/scalar_test.cc
@@ -973,6 +973,51 @@ TEST(TestDateScalars, MakeScalar) {
Date64Scalar(-188171LL * 24 * 60 * 60 * 1000));
}
+TEST(TestDateScalars, CastTo) {
+ constexpr int64_t kMillisecondsInDay = 86400000;
+
+ ASSERT_OK_AND_ASSIGN(auto casted_date64, Date32Scalar(2).CastTo(date64()));
+ EXPECT_EQ(*casted_date64, Date64Scalar(2 * kMillisecondsInDay));
+
+ ASSERT_OK_AND_ASSIGN(auto casted_date32,
+ Date64Scalar(2 * kMillisecondsInDay).CastTo(date32()));
+ EXPECT_EQ(*casted_date32, Date32Scalar(2));
+
+ const auto timestamp_type = timestamp(TimeUnit::SECOND);
+
+ ASSERT_OK_AND_ASSIGN(auto timestamp_from_date32,
+ Date32Scalar(2).CastTo(timestamp_type));
+ EXPECT_EQ(*timestamp_from_date32, TimestampScalar(2 * 24 * 60 * 60,
timestamp_type));
+
+ ASSERT_OK_AND_ASSIGN(auto timestamp_from_date64,
+ Date64Scalar(2 *
kMillisecondsInDay).CastTo(timestamp_type));
+ EXPECT_EQ(*timestamp_from_date64, TimestampScalar(2 * 24 * 60 * 60,
timestamp_type));
+
+ ASSERT_OK_AND_ASSIGN(
+ auto date64_from_timestamp,
+ TimestampScalar(2 * kMillisecondsInDay + 3, timestamp(TimeUnit::MILLI))
+ .CastTo(date64()));
+ EXPECT_EQ(*date64_from_timestamp, Date64Scalar(2 * kMillisecondsInDay));
+
+ ASSERT_OK_AND_ASSIGN(
+ auto date32_from_timestamp,
+ TimestampScalar(2 * kMillisecondsInDay + 3, timestamp(TimeUnit::MILLI))
+ .CastTo(date32()));
+ EXPECT_EQ(*date32_from_timestamp, Date32Scalar(2));
+
+ // 1969-12-31T12:00:00Z -- floor to the previous day instead of truncating
to zero.
+ constexpr int64_t kHalfDay = kMillisecondsInDay / 2;
+ ASSERT_OK_AND_ASSIGN(
+ auto negative_date64_from_timestamp,
+ TimestampScalar(-kHalfDay, timestamp(TimeUnit::MILLI)).CastTo(date64()));
+ EXPECT_EQ(*negative_date64_from_timestamp,
Date64Scalar(-kMillisecondsInDay));
+
+ ASSERT_OK_AND_ASSIGN(
+ auto negative_date32_from_timestamp,
+ TimestampScalar(-kHalfDay, timestamp(TimeUnit::MILLI)).CastTo(date32()));
+ EXPECT_EQ(*negative_date32_from_timestamp, Date32Scalar(-1));
+}
+
TEST(TestTimeScalars, Basics) {
auto type1 = time32(TimeUnit::MILLI);
auto type2 = time32(TimeUnit::SECOND);
diff --git a/cpp/src/arrow/util/async_generator.h
b/cpp/src/arrow/util/async_generator.h
index 63cb01a9fe4..8b2c4dc54d9 100644
--- a/cpp/src/arrow/util/async_generator.h
+++ b/cpp/src/arrow/util/async_generator.h
@@ -1863,7 +1863,7 @@ constexpr int kDefaultBackgroundQRestart = 16;
///
/// This generator will queue up to max_q blocks
template <typename T>
-static Result<AsyncGenerator<T>> MakeBackgroundGenerator(
+Result<AsyncGenerator<T>> MakeBackgroundGenerator(
Iterator<T> iterator, internal::Executor* io_executor,
int max_q = kDefaultBackgroundMaxQ, int q_restart =
kDefaultBackgroundQRestart) {
if (max_q < q_restart) {
@@ -1887,15 +1887,14 @@ static Result<AsyncGenerator<T>>
MakeBackgroundGenerator(
///
/// This generator does not queue
template <typename T>
-static Result<AsyncGenerator<T>> MakeBlockingGenerator(
- std::shared_ptr<Iterator<T>> iterator) {
+Result<AsyncGenerator<T>> MakeBlockingGenerator(std::shared_ptr<Iterator<T>>
iterator) {
return [it = std::move(iterator)]() mutable -> Future<T> {
return Future<T>::MakeFinished(it->Next());
};
}
template <typename T>
-static Result<AsyncGenerator<T>> MakeBlockingGenerator(Iterator<T> iterator) {
+Result<AsyncGenerator<T>> MakeBlockingGenerator(Iterator<T> iterator) {
return
MakeBlockingGenerator(std::make_shared<Iterator<T>>(std::move(iterator)));
}
diff --git a/cpp/src/arrow/util/bit_block_counter.h
b/cpp/src/arrow/util/bit_block_counter.h
index 82651a9d387..ec01ac84aca 100644
--- a/cpp/src/arrow/util/bit_block_counter.h
+++ b/cpp/src/arrow/util/bit_block_counter.h
@@ -425,8 +425,8 @@ class ARROW_EXPORT OptionalBinaryBitBlockCounter {
// Functional-style bit block visitors.
template <typename VisitNotNull, typename VisitNull>
-static Status VisitBitBlocks(const uint8_t* bitmap, int64_t offset, int64_t
length,
- VisitNotNull&& visit_not_null, VisitNull&&
visit_null) {
+Status VisitBitBlocks(const uint8_t* bitmap, int64_t offset, int64_t length,
+ VisitNotNull&& visit_not_null, VisitNull&& visit_null) {
internal::OptionalBitBlockCounter bit_counter(bitmap, offset, length);
int64_t position = 0;
while (position < length) {
@@ -453,8 +453,8 @@ static Status VisitBitBlocks(const uint8_t* bitmap, int64_t
offset, int64_t leng
}
template <typename VisitNotNull, typename VisitNull>
-static void VisitBitBlocksVoid(const uint8_t* bitmap, int64_t offset, int64_t
length,
- VisitNotNull&& visit_not_null, VisitNull&&
visit_null) {
+void VisitBitBlocksVoid(const uint8_t* bitmap, int64_t offset, int64_t length,
+ VisitNotNull&& visit_not_null, VisitNull&& visit_null)
{
internal::OptionalBitBlockCounter bit_counter(bitmap, offset, length);
int64_t position = 0;
while (position < length) {
@@ -480,10 +480,10 @@ static void VisitBitBlocksVoid(const uint8_t* bitmap,
int64_t offset, int64_t le
}
template <typename VisitNotNull, typename VisitNull>
-static Status VisitTwoBitBlocks(const uint8_t* left_bitmap, int64_t
left_offset,
- const uint8_t* right_bitmap, int64_t
right_offset,
- int64_t length, VisitNotNull&& visit_not_null,
- VisitNull&& visit_null) {
+Status VisitTwoBitBlocks(const uint8_t* left_bitmap, int64_t left_offset,
+ const uint8_t* right_bitmap, int64_t right_offset,
+ int64_t length, VisitNotNull&& visit_not_null,
+ VisitNull&& visit_null) {
if (left_bitmap == NULLPTR || right_bitmap == NULLPTR) {
// At most one bitmap is present
if (left_bitmap == NULLPTR) {
@@ -524,10 +524,10 @@ static Status VisitTwoBitBlocks(const uint8_t*
left_bitmap, int64_t left_offset,
}
template <typename VisitNotNull, typename VisitNull>
-static void VisitTwoBitBlocksVoid(const uint8_t* left_bitmap, int64_t
left_offset,
- const uint8_t* right_bitmap, int64_t
right_offset,
- int64_t length, VisitNotNull&&
visit_not_null,
- VisitNull&& visit_null) {
+void VisitTwoBitBlocksVoid(const uint8_t* left_bitmap, int64_t left_offset,
+ const uint8_t* right_bitmap, int64_t right_offset,
+ int64_t length, VisitNotNull&& visit_not_null,
+ VisitNull&& visit_null) {
if (left_bitmap == NULLPTR || right_bitmap == NULLPTR) {
// At most one bitmap is present
if (left_bitmap == NULLPTR) {
diff --git a/cpp/src/arrow/util/future.h b/cpp/src/arrow/util/future.h
index 1884f596979..0210350d538 100644
--- a/cpp/src/arrow/util/future.h
+++ b/cpp/src/arrow/util/future.h
@@ -693,7 +693,7 @@ class WeakFuture {
/// If a Result<Future> holds an error instead of a Future, construct a
finished Future
/// holding that error.
template <typename T>
-static Future<T> DeferNotOk(Result<Future<T>> maybe_future) {
+Future<T> DeferNotOk(Result<Future<T>> maybe_future) {
if (ARROW_PREDICT_FALSE(!maybe_future.ok())) {
return Future<T>::MakeFinished(std::move(maybe_future).status());
}
diff --git a/cpp/src/gandiva/decimal_ir.cc b/cpp/src/gandiva/decimal_ir.cc
index 30096e4d285..36e423e0647 100644
--- a/cpp/src/gandiva/decimal_ir.cc
+++ b/cpp/src/gandiva/decimal_ir.cc
@@ -23,6 +23,7 @@
#include "arrow/util/logging_internal.h"
#include "gandiva/decimal_ir.h"
#include "gandiva/decimal_type_util.h"
+#include "gandiva/llvm_util_internal.h"
// Algorithms adapted from Apache Impala
@@ -424,9 +425,15 @@ Status DecimalIR::BuildCompare(const std::string&
function_name,
llvm::Value* DecimalIR::CallDecimalFunction(const std::string& function_name,
llvm::Type* return_type,
const std::vector<llvm::Value*>&
params) {
+ auto create_call = [&](const std::vector<llvm::Value*>& args) {
+ auto* call =
ir_builder()->CreateCall(module()->getFunction(function_name), args);
+ internal::CopyZExtAttrs(*call->getCalledFunction(), *call);
+ return call;
+ };
+
if (kDecimalIRBuilderFunctions.count(function_name) != 0) {
// this is fn built with the irbuilder.
- return ir_builder()->CreateCall(module()->getFunction(function_name),
params);
+ return create_call(params);
}
// ppre-compiler fn : disassemble i128 to two i64s and re-assemble.
@@ -454,7 +461,7 @@ llvm::Value* DecimalIR::CallDecimalFunction(const
std::string& function_name,
dis_assembled_args.push_back(out_low_ptr);
// Make call to pre-compiled IR function.
- ir_builder()->CreateCall(module()->getFunction(function_name),
dis_assembled_args);
+ create_call(dis_assembled_args);
auto out_high = ir_builder()->CreateLoad(i64, out_high_ptr);
auto out_low = ir_builder()->CreateLoad(i64, out_low_ptr);
@@ -463,8 +470,7 @@ llvm::Value* DecimalIR::CallDecimalFunction(const
std::string& function_name,
DCHECK_NE(return_type, types()->void_type());
// Make call to pre-compiled IR function.
- result = ir_builder()->CreateCall(module()->getFunction(function_name),
- dis_assembled_args);
+ result = create_call(dis_assembled_args);
}
return result;
}
diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc
index e6edb0042ce..205d2f866b2 100644
--- a/cpp/src/gandiva/engine.cc
+++ b/cpp/src/gandiva/engine.cc
@@ -113,6 +113,7 @@
#include "gandiva/decimal_ir.h"
#include "gandiva/exported_funcs.h"
#include "gandiva/exported_funcs_registry.h"
+#include "gandiva/llvm_util_internal.h"
namespace gandiva {
@@ -131,6 +132,8 @@ template <typename T>
arrow::Result<T> AsArrowResult(llvm::Expected<T>& expected,
const std::string& error_context) {
if (!expected) {
+ // NOTE: llvm::handleAllErrors() fails linking with RTTI-disabled LLVM
builds
+ // (ARROW-5148)
return Status::CodeGenError(error_context,
llvm::toString(expected.takeError()));
}
return std::move(expected.get());
@@ -198,24 +201,34 @@ void AddProcessSymbol(llvm::orc::LLJIT& lljit) {
}
#ifdef JIT_LINK_SUPPORTED
-Result<std::unique_ptr<llvm::jitlink::InProcessMemoryManager>>
CreateMemmoryManager() {
+# if LLVM_VERSION_MAJOR < 23
+Result<std::unique_ptr<llvm::jitlink::InProcessMemoryManager>>
CreateMemoryManager() {
auto maybe_mem_manager = llvm::jitlink::InProcessMemoryManager::Create();
return AsArrowResult(maybe_mem_manager, "Could not create memory manager: ");
}
+# endif
Status UseJITLinkIfEnabled(llvm::orc::LLJITBuilder& jit_builder) {
static auto maybe_use_jit_link =
::arrow::internal::GetEnvVar("GANDIVA_USE_JIT_LINK");
if (maybe_use_jit_link.ok()) {
- ARROW_ASSIGN_OR_RAISE(static auto memory_manager, CreateMemmoryManager());
-# if LLVM_VERSION_MAJOR >= 21
+# if LLVM_VERSION_MAJOR >= 23
+ jit_builder.setObjectLinkingLayerCreator(
+ [](llvm::orc::ExecutionSession& ES,
+ llvm::jitlink::JITLinkMemoryManager& memory_manager) {
+ return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES,
memory_manager);
+ });
+# else
+ ARROW_ASSIGN_OR_RAISE(static auto memory_manager, CreateMemoryManager());
+# if LLVM_VERSION_MAJOR >= 21
jit_builder.setObjectLinkingLayerCreator([&](llvm::orc::ExecutionSession&
ES) {
return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES,
*memory_manager);
});
-# else
+# else
jit_builder.setObjectLinkingLayerCreator(
[&](llvm::orc::ExecutionSession& ES, const llvm::Triple& TT) {
return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES,
*memory_manager);
});
+# endif
# endif
}
return Status::OK();
@@ -261,13 +274,8 @@ Result<std::unique_ptr<llvm::orc::LLJIT>> BuildJIT(
return jit;
}
-arrow::Status VerifyAndLinkModule(
- llvm::Module& dest_module,
- llvm::Expected<std::unique_ptr<llvm::Module>> src_module_or_error) {
- ARROW_ASSIGN_OR_RAISE(
- auto src_ir_module,
- AsArrowResult(src_module_or_error, "Failed to verify and link module:
"));
-
+arrow::Status VerifyAndLinkModule(llvm::Module& dest_module,
+ std::unique_ptr<llvm::Module> src_ir_module)
{
src_ir_module->setDataLayout(dest_module.getDataLayout());
std::string error_info;
@@ -282,6 +290,18 @@ arrow::Status VerifyAndLinkModule(
return Status::OK();
}
+void RemoveBuildTargetAttributes(llvm::Module& module) {
+ // Precompiled bitcode is JIT-compiled on the runtime host, so do not retain
the
+ // target selected when the bitcode was built. LLVM 23 checks target-feature
+ // compatibility even for alwaysinline functions and prevents inlining on a
mismatch.
+ // See
https://releases.llvm.org/23.1.0/docs/ReleaseNotes.html#changes-to-the-llvm-ir.
+ for (auto& function : module.functions()) {
+ function.removeFnAttr("target-cpu");
+ function.removeFnAttr("target-features");
+ function.removeFnAttr("tune-cpu");
+ }
+}
+
} // namespace
Status Engine::SetLLVMObjectCache(GandivaObjectCache& object_cache) {
@@ -422,10 +442,11 @@ Status Engine::LoadPreCompiledIR() {
/// Parse the IR module.
llvm::Expected<std::unique_ptr<llvm::Module>> module_or_error =
llvm::getOwningLazyBitcodeModule(std::move(buffer), *context());
- // NOTE: llvm::handleAllErrors() fails linking with RTTI-disabled LLVM builds
- // (ARROW-5148)
- ARROW_RETURN_NOT_OK(VerifyAndLinkModule(*module_,
std::move(module_or_error)));
- return Status::OK();
+ ARROW_ASSIGN_OR_RAISE(
+ auto src_ir_module,
+ AsArrowResult(module_or_error, "Failed to verify and link module: "));
+ RemoveBuildTargetAttributes(*src_ir_module);
+ return VerifyAndLinkModule(*module_, std::move(src_ir_module));
}
static llvm::MemoryBufferRef AsLLVMMemoryBuffer(const arrow::Buffer&
arrow_buffer) {
@@ -439,7 +460,11 @@ Status Engine::LoadExternalPreCompiledIR() {
for (const auto& buffer : buffers) {
auto llvm_memory_buffer_ref = AsLLVMMemoryBuffer(*buffer);
auto module_or_error = llvm::parseBitcodeFile(llvm_memory_buffer_ref,
*context());
- ARROW_RETURN_NOT_OK(VerifyAndLinkModule(*module_,
std::move(module_or_error)));
+ ARROW_ASSIGN_OR_RAISE(
+ auto src_ir_module,
+ AsArrowResult(module_or_error, "Failed to verify and link module: "));
+ RemoveBuildTargetAttributes(*src_ir_module);
+ ARROW_RETURN_NOT_OK(VerifyAndLinkModule(*module_,
std::move(src_ir_module)));
}
return Status::OK();
@@ -601,7 +626,11 @@ Result<void*> Engine::CompiledFunction(const std::string&
function) {
void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type*
ret_type,
const std::vector<llvm::Type*>& args,
void* func) {
const auto prototype = llvm::FunctionType::get(ret_type, args,
/*is_var_arg*/ false);
- llvm::Function::Create(prototype, llvm::GlobalValue::ExternalLinkage, name,
module());
+ auto* function = llvm::Function::Create(prototype,
llvm::GlobalValue::ExternalLinkage,
+ name, module());
+ // TODO: Other native function mappings may require target-specific ABI
attributes
+ // that cannot be inferred from their LLVM types alone.
+ internal::AddNativeBoolZExtAttrs(*function);
AddAbsoluteSymbol(*lljit_, name, func);
}
diff --git a/cpp/src/gandiva/llvm_generator.cc
b/cpp/src/gandiva/llvm_generator.cc
index a42f71a1f7f..7caafa58369 100644
--- a/cpp/src/gandiva/llvm_generator.cc
+++ b/cpp/src/gandiva/llvm_generator.cc
@@ -28,6 +28,7 @@
#include "gandiva/expr_decomposer.h"
#include "gandiva/expression.h"
#include "gandiva/llvm_types.h"
+#include "gandiva/llvm_util_internal.h"
#include "gandiva/lvalue.h"
namespace gandiva {
@@ -543,16 +544,17 @@ llvm::Value* LLVMGenerator::AddFunctionCall(const
std::string& full_name,
}
// build a call to the llvm function.
- llvm::Value* value;
+ llvm::CallInst* call;
if (ret_type->isVoidTy()) {
// void functions can't have a name for the call.
- value = ir_builder()->CreateCall(fn, args);
+ call = ir_builder()->CreateCall(fn, args);
} else {
- value = ir_builder()->CreateCall(fn, args, full_name);
- DCHECK(value->getType() == ret_type);
+ call = ir_builder()->CreateCall(fn, args, full_name);
+ DCHECK(call->getType() == ret_type);
}
+ internal::CopyZExtAttrs(*fn, *call);
- return value;
+ return call;
}
std::shared_ptr<DecimalLValue> LLVMGenerator::BuildDecimalLValue(llvm::Value*
value,
diff --git a/cpp/src/gandiva/llvm_generator.h b/cpp/src/gandiva/llvm_generator.h
index a60e2bf6b29..c6e4e821dfe 100644
--- a/cpp/src/gandiva/llvm_generator.h
+++ b/cpp/src/gandiva/llvm_generator.h
@@ -91,6 +91,7 @@ class GANDIVA_EXPORT LLVMGenerator {
FRIEND_TEST(TestLLVMGenerator, VerifyPCFunctions);
FRIEND_TEST(TestLLVMGenerator, TestAdd);
+ FRIEND_TEST(TestLLVMGenerator, TestBoolCallAttrs);
FRIEND_TEST(TestLLVMGenerator, TestNullInternal);
friend class TestLLVMGenerator;
diff --git a/cpp/src/gandiva/llvm_generator_test.cc
b/cpp/src/gandiva/llvm_generator_test.cc
index b3aa0465e6c..0d8206e0d63 100644
--- a/cpp/src/gandiva/llvm_generator_test.cc
+++ b/cpp/src/gandiva/llvm_generator_test.cc
@@ -27,7 +27,9 @@
#include "gandiva/expression.h"
#include "gandiva/func_descriptor.h"
#include "gandiva/function_registry.h"
+#include "gandiva/llvm_util_internal.h"
#include "gandiva/tests/test_util.h"
+#include "gandiva/tree_expr_builder.h"
namespace gandiva {
@@ -66,6 +68,61 @@ TEST_F(TestLLVMGenerator, VerifyPCFunctions) {
}
}
+TEST_F(TestLLVMGenerator, TestBoolCallAttrs) {
+ ASSERT_OK_AND_ASSIGN(auto generator,
LLVMGenerator::Make(TestConfiguration(), false));
+
+ // Check that native bool declarations carry the ABI attributes.
+ auto* function =
generator->module()->getFunction("gdv_fn_in_expr_lookup_int32");
+ ASSERT_NE(function, nullptr);
+ EXPECT_TRUE(internal::HasRetAttr(function->getAttributes(),
llvm::Attribute::ZExt));
+ EXPECT_TRUE(function->hasParamAttribute(2, llvm::Attribute::ZExt));
+
+ auto* types = generator->types();
+ auto* prototype = llvm::FunctionType::get(types->void_type(),
/*is_var_arg=*/false);
+ auto* caller = llvm::Function::Create(prototype,
llvm::GlobalValue::ExternalLinkage,
+ "bool_call_attrs",
generator->module());
+ auto* entry = llvm::BasicBlock::Create(*generator->context(), "entry",
caller);
+ generator->ir_builder()->SetInsertPoint(entry);
+
+ // Check that AddFunctionCall copies the attributes to the call site.
+ auto* call = llvm::cast<llvm::CallInst>(generator->AddFunctionCall(
+ "gdv_fn_in_expr_lookup_int32", types->i1_type(),
+ {types->i64_constant(0), types->i32_constant(0),
types->true_constant()}));
+ EXPECT_TRUE(internal::HasRetAttr(call->getAttributes(),
llvm::Attribute::ZExt));
+ EXPECT_TRUE(call->getAttributes().hasParamAttr(2, llvm::Attribute::ZExt));
+
+ // Check that ordinary LLVM i1 functions do not receive the attributes.
+ auto* i1_prototype =
+ llvm::FunctionType::get(types->i1_type(), {types->i1_type()}, false);
+ llvm::Function::Create(i1_prototype, llvm::GlobalValue::ExternalLinkage,
"plain_i1",
+ generator->module());
+ auto* i1_call = llvm::cast<llvm::CallInst>(
+ generator->AddFunctionCall("plain_i1", types->i1_type(),
{types->true_constant()}));
+ generator->ir_builder()->CreateRetVoid();
+ EXPECT_FALSE(internal::HasRetAttr(i1_call->getAttributes(),
llvm::Attribute::ZExt));
+ EXPECT_FALSE(i1_call->getAttributes().hasParamAttr(0,
llvm::Attribute::ZExt));
+}
+
+TEST_F(TestLLVMGenerator, TestDecimalCallAttrs) {
+ ASSERT_OK_AND_ASSIGN(auto generator,
+ LLVMGenerator::Make(TestConfigWithIrDumping(), false));
+
+ // Check that the call emitted through DecimalIR has zeroext on the result
and the
+ // native bool argument after its i128 argument is split.
+ constexpr int32_t precision = 38;
+ constexpr int32_t scale = 5;
+ auto field = arrow::field("decimal", arrow::decimal128(precision, scale));
+ auto field_node = TreeExprBuilder::MakeField(field);
+ std::unordered_set<DecimalScalar128> constants{DecimalScalar128("6",
precision, scale)};
+ auto in_node = TreeExprBuilder::MakeInExpressionDecimal(field_node,
constants);
+ auto condition = TreeExprBuilder::MakeCondition(in_node);
+ ASSERT_OK(generator->Build({condition}));
+
+ EXPECT_THAT(generator->ir(),
+ testing::ContainsRegex(
+ R"(call zeroext i1 @gdv_fn_in_expr_lookup_decimal\(.*, i1
zeroext)"));
+}
+
TEST_F(TestLLVMGenerator, TestAdd) {
// Setup LLVM generator to do an arithmetic add of two vectors
ASSERT_OK_AND_ASSIGN(auto generator,
diff --git a/cpp/src/gandiva/llvm_util_internal.h
b/cpp/src/gandiva/llvm_util_internal.h
new file mode 100644
index 00000000000..e114e87fd80
--- /dev/null
+++ b/cpp/src/gandiva/llvm_util_internal.h
@@ -0,0 +1,84 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#pragma once
+
+#include "gandiva/llvm_includes.h"
+
+namespace gandiva::internal {
+
+inline bool HasRetAttr(const llvm::AttributeList& attributes,
+ llvm::Attribute::AttrKind kind) {
+#if LLVM_VERSION_MAJOR >= 14
+ return attributes.hasRetAttr(kind);
+#else
+ return attributes.hasAttribute(llvm::AttributeList::ReturnIndex, kind);
+#endif
+}
+
+inline llvm::AttributeList AddRetAttr(llvm::LLVMContext& context,
+ const llvm::AttributeList& attributes,
+ llvm::Attribute::AttrKind kind) {
+#if LLVM_VERSION_MAJOR >= 14
+ return attributes.addRetAttribute(context, kind);
+#else
+ return attributes.addAttribute(context, llvm::AttributeList::ReturnIndex,
kind);
+#endif
+}
+
+inline void AddNativeBoolZExtAttrs(llvm::Function& function) {
+ // Gandiva uses i1 parameters and results in native C++ mappings only for
bool.
+ const auto* function_type = function.getFunctionType();
+ if (function_type->getReturnType()->isIntegerTy(1)) {
+ // A native bool result must be zero-extended by the callee before it
crosses
+ // the ABI boundary. This matches Clang's lowering of C++ bool.
+ function.setAttributes(AddRetAttr(function.getContext(),
function.getAttributes(),
+ llvm::Attribute::ZExt));
+ }
+
+ for (unsigned i = 0; i < function_type->getNumParams(); ++i) {
+ if (function_type->getParamType(i)->isIntegerTy(1)) {
+ // The caller must pass a native bool as 0 or 1.
+ // LLVM 23 can replace `icmp ne (and X, 1), 0` with `trunc X to i1`; i1
only defines
+ // bit 0, so this ABI attribute is required to normalize the value at
the call.
+ // https://github.com/llvm/llvm-project/pull/178977
+ function.addParamAttr(i, llvm::Attribute::ZExt);
+ }
+ }
+}
+
+inline void CopyZExtAttrs(const llvm::Function& function, llvm::CallInst&
call) {
+ // https://llvm.org/docs/LangRef.html#parameter-attributes
+ // "ABI attributes must be specified both at the function
declaration/definition and
+ // call-site, otherwise the behavior may be undefined. ABI attributes cannot
be safely
+ // dropped."
+ //
+ // TODO: Copy other ABI attributes as well. This currently copies only
`zeroext`,
+ // which is required for Gandiva's native bool parameters and results.
+ if (HasRetAttr(function.getAttributes(), llvm::Attribute::ZExt)) {
+ call.setAttributes(
+ AddRetAttr(call.getContext(), call.getAttributes(),
llvm::Attribute::ZExt));
+ }
+
+ for (unsigned i = 0; i < function.arg_size(); ++i) {
+ if (function.hasParamAttribute(i, llvm::Attribute::ZExt)) {
+ call.addParamAttr(i, llvm::Attribute::ZExt);
+ }
+ }
+}
+
+} // namespace gandiva::internal