| // 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. |
| |
| #include <memory> |
| #include <string> |
| #include <boost/thread/thread.hpp> |
| |
| #include "testutil/gtest-util.h" |
| #include "codegen/llvm-codegen.h" |
| #include "common/init.h" |
| #include "common/object-pool.h" |
| #include "runtime/string-value.h" |
| #include "runtime/test-env.h" |
| #include "service/fe-support.h" |
| #include "util/cpu-info.h" |
| #include "util/filesystem-util.h" |
| #include "util/hash-util.h" |
| #include "util/path-builder.h" |
| #include "util/scope-exit-trigger.h" |
| #include "util/test-info.h" |
| |
| #include "common/names.h" |
| |
| using std::unique_ptr; |
| |
| namespace impala { |
| |
| class LlvmCodeGenTest : public testing:: Test { |
| protected: |
| scoped_ptr<TestEnv> test_env_; |
| RuntimeState* runtime_state_; |
| |
| virtual void SetUp() { |
| test_env_.reset(new TestEnv()); |
| ASSERT_OK(test_env_->Init()); |
| ASSERT_OK(test_env_->CreateQueryState(0, nullptr, &runtime_state_)); |
| } |
| |
| virtual void TearDown() { |
| runtime_state_ = NULL; |
| test_env_.reset(); |
| } |
| |
| static void LifetimeTest() { |
| ObjectPool pool; |
| Status status; |
| for (int i = 0; i < 10; ++i) { |
| LlvmCodeGen object1(NULL, &pool, NULL, "Test"); |
| LlvmCodeGen object2(NULL, &pool, NULL, "Test"); |
| LlvmCodeGen object3(NULL, &pool, NULL, "Test"); |
| |
| ASSERT_OK(object1.Init( |
| unique_ptr<llvm::Module>(new llvm::Module("Test", object1.context())))); |
| ASSERT_OK(object2.Init( |
| unique_ptr<llvm::Module>(new llvm::Module("Test", object2.context())))); |
| ASSERT_OK(object3.Init( |
| unique_ptr<llvm::Module>(new llvm::Module("Test", object3.context())))); |
| |
| object1.Close(); |
| object2.Close(); |
| object3.Close(); |
| } |
| } |
| |
| // Wrapper to call private test-only methods on LlvmCodeGen object |
| Status CreateFromFile(const string& filename, scoped_ptr<LlvmCodeGen>* codegen) { |
| RETURN_IF_ERROR(LlvmCodeGen::CreateFromFile(runtime_state_, |
| runtime_state_->obj_pool(), NULL, filename, "test", codegen)); |
| return (*codegen)->MaterializeModule(); |
| } |
| |
| static void ClearHashFns(LlvmCodeGen* codegen) { |
| codegen->ClearHashFns(); |
| } |
| |
| static void AddFunctionToJit(LlvmCodeGen* codegen, llvm::Function* fn, void** fn_ptr) { |
| // Bypass Impala-specific logic in AddFunctionToJit() that assumes Impala's struct |
| // types are available in the module. |
| return codegen->AddFunctionToJitInternal(fn, fn_ptr); |
| } |
| |
| static bool VerifyFunction(LlvmCodeGen* codegen, llvm::Function* fn) { |
| return codegen->VerifyFunction(fn); |
| } |
| |
| static Status FinalizeModule(LlvmCodeGen* codegen) { return codegen->FinalizeModule(); } |
| |
| static Status LinkModuleFromLocalFs(LlvmCodeGen* codegen, const string& file) { |
| return codegen->LinkModuleFromLocalFs(file); |
| } |
| |
| static Status LinkModuleFromHdfs(LlvmCodeGen* codegen, const string& hdfs_file) { |
| return codegen->LinkModuleFromHdfs(hdfs_file, -1); |
| } |
| |
| static bool ContainsHandcraftedFn(LlvmCodeGen* codegen, llvm::Function* function) { |
| const auto& hf = codegen->handcrafted_functions_; |
| return find(hf.begin(), hf.end(), function) != hf.end(); |
| } |
| |
| // Helper function to enable and disable LlvmCodeGen's cpu_attrs_. |
| static void EnableCodeGenCPUFeature(int64_t flag, bool enable) { |
| DCHECK(LlvmCodeGen::llvm_initialized_); |
| auto enable_flag_it = LlvmCodeGen::cpu_flag_mappings_.find(flag); |
| auto disable_flag_it = LlvmCodeGen::cpu_flag_mappings_.find(~flag); |
| DCHECK(enable_flag_it != LlvmCodeGen::cpu_flag_mappings_.end()); |
| DCHECK(disable_flag_it != LlvmCodeGen::cpu_flag_mappings_.end()); |
| const std::string& enable_feature = enable_flag_it->second; |
| const std::string& disable_feature = disable_flag_it->second; |
| if (enable) { |
| auto attr_it = LlvmCodeGen::cpu_attrs_.find(disable_feature); |
| if (attr_it != LlvmCodeGen::cpu_attrs_.end()) { |
| LlvmCodeGen::cpu_attrs_.erase(attr_it); |
| } |
| LlvmCodeGen::cpu_attrs_.insert(enable_feature); |
| } else { |
| auto attr_it = LlvmCodeGen::cpu_attrs_.find(enable_feature); |
| // Disable feature if currently enabled. |
| if (attr_it != LlvmCodeGen::cpu_attrs_.end()) { |
| LlvmCodeGen::cpu_attrs_.erase(attr_it); |
| LlvmCodeGen::cpu_attrs_.insert(disable_feature); |
| } |
| } |
| } |
| }; |
| |
| // Simple test to just make and destroy llvmcodegen objects. LLVM |
| // has non-obvious object ownership transfers and this sanity checks that. |
| TEST_F(LlvmCodeGenTest, BasicLifetime) { |
| LifetimeTest(); |
| } |
| |
| // Same as above but multithreaded |
| TEST_F(LlvmCodeGenTest, MultithreadedLifetime) { |
| const int NUM_THREADS = 10; |
| thread_group thread_group; |
| for (int i = 0; i < NUM_THREADS; ++i) { |
| thread_group.add_thread(new thread(&LifetimeTest)); |
| } |
| thread_group.join_all(); |
| } |
| |
| // Test loading a non-existent file |
| TEST_F(LlvmCodeGenTest, BadIRFile) { |
| string module_file = "NonExistentFile.ir"; |
| scoped_ptr<LlvmCodeGen> codegen; |
| EXPECT_FALSE(CreateFromFile(module_file.c_str(), &codegen).ok()); |
| codegen->Close(); |
| } |
| |
| // IR for the generated linner loop |
| // define void @JittedInnerLoop() { |
| // entry: |
| // call void @DebugTrace(i8* inttoptr (i64 18970856 to i8*)) |
| // %0 = load i64* inttoptr (i64 140735197627800 to i64*) |
| // %1 = add i64 %0, <delta> |
| // store i64 %1, i64* inttoptr (i64 140735197627800 to i64*) |
| // ret void |
| // } |
| // The random int in there is the address of jitted_counter |
| llvm::Function* CodegenInnerLoop( |
| LlvmCodeGen* codegen, int64_t* jitted_counter, int delta) { |
| llvm::LLVMContext& context = codegen->context(); |
| LlvmBuilder builder(context); |
| |
| LlvmCodeGen::FnPrototype fn_prototype(codegen, "JittedInnerLoop", codegen->void_type()); |
| llvm::Function* jitted_loop_call = fn_prototype.GeneratePrototype(); |
| llvm::BasicBlock* entry_block = |
| llvm::BasicBlock::Create(context, "entry", jitted_loop_call); |
| builder.SetInsertPoint(entry_block); |
| codegen->CodegenDebugTrace(&builder, "Jitted\n"); |
| |
| // Store &jitted_counter as a constant. |
| llvm::Value* const_delta = codegen->GetI64Constant(delta); |
| llvm::Value* counter_ptr = |
| codegen->CastPtrToLlvmPtr(codegen->i64_ptr_type(), jitted_counter); |
| llvm::Value* loaded_counter = builder.CreateLoad(counter_ptr); |
| llvm::Value* incremented_value = builder.CreateAdd(loaded_counter, const_delta); |
| builder.CreateStore(incremented_value, counter_ptr); |
| builder.CreateRetVoid(); |
| |
| return codegen->FinalizeFunction(jitted_loop_call); |
| } |
| |
| // This test loads a precompiled IR file (compiled from testdata/llvm/test-loop.cc). |
| // The test contains two functions, an outer loop function and an inner loop function. |
| // The outer loop calls the inner loop function. |
| // The test will |
| // 1. create a LlvmCodegen object from the precompiled file |
| // 2. add another function to the module with the same signature as the inner |
| // loop function. |
| // 3. Replace the call instruction in a clone of the outer loop to a call to the new |
| // inner loop function. |
| // 4. Update the original loop with another jitted inner loop function |
| // 5. Run both loops and make sure the correct inner loop is called |
| |
| // 4. Run the loop and make sure the inner loop is called. |
| // 5. Updated the jitted loop in place with another jitted inner loop function |
| // 6. Run the loop and make sure the updated is called. |
| TEST_F(LlvmCodeGenTest, ReplaceFnCall) { |
| const string loop_call_name("_Z21DefaultImplementationv"); |
| const string loop_name("_Z8TestLoopi"); |
| typedef void (*TestLoopFn)(int); |
| |
| string module_file; |
| PathBuilder::GetFullPath("llvm-ir/test-loop.bc", &module_file); |
| |
| // Part 1: Load the module and make sure everything is loaded correctly. |
| scoped_ptr<LlvmCodeGen> codegen; |
| ASSERT_OK(CreateFromFile(module_file.c_str(), &codegen)); |
| EXPECT_TRUE(codegen.get() != NULL); |
| |
| llvm::Function* loop_call = codegen->GetFunction(loop_call_name, false); |
| EXPECT_TRUE(loop_call != NULL); |
| EXPECT_TRUE(loop_call->arg_empty()); |
| llvm::Function* loop = codegen->GetFunction(loop_name, false); |
| EXPECT_TRUE(loop != NULL); |
| EXPECT_EQ(loop->arg_size(), 1); |
| |
| // Part 2: Generate a new inner loop function. |
| // |
| // The c++ version of the code is |
| // static int64_t* counter; |
| // void JittedInnerLoop() { |
| // printf("LLVM Trace: Jitted\n"); |
| // ++*counter; |
| // } |
| // |
| int64_t jitted_counter = 0; |
| llvm::Function* jitted_loop_call = CodegenInnerLoop(codegen.get(), &jitted_counter, 1); |
| |
| // Part 3: Clone 'loop' and replace the call instruction to the normal function with a |
| // call to the jitted one |
| llvm::Function* jitted_loop = codegen->CloneFunction(loop); |
| int num_replaced = |
| codegen->ReplaceCallSites(jitted_loop, jitted_loop_call, loop_call_name); |
| EXPECT_EQ(1, num_replaced); |
| EXPECT_TRUE(VerifyFunction(codegen.get(), jitted_loop)); |
| |
| // Part 4: Generate a new inner loop function and a new loop function |
| llvm::Function* jitted_loop_call2 = |
| CodegenInnerLoop(codegen.get(), &jitted_counter, -2); |
| llvm::Function* jitted_loop2 = codegen->CloneFunction(loop); |
| num_replaced = codegen->ReplaceCallSites(jitted_loop2, jitted_loop_call2, loop_call_name); |
| EXPECT_EQ(1, num_replaced); |
| EXPECT_TRUE(VerifyFunction(codegen.get(), jitted_loop2)); |
| |
| void* original_loop = NULL; |
| AddFunctionToJit(codegen.get(), loop, &original_loop); |
| void* new_loop = NULL; |
| AddFunctionToJit(codegen.get(), jitted_loop, &new_loop); |
| void* new_loop2 = NULL; |
| AddFunctionToJit(codegen.get(), jitted_loop2, &new_loop2); |
| |
| // Part 5: compile all the functions (we can't add more functions after jitting with |
| // MCJIT) then run them. |
| ASSERT_OK(LlvmCodeGenTest::FinalizeModule(codegen.get())); |
| ASSERT_TRUE(original_loop != NULL); |
| ASSERT_TRUE(new_loop != NULL); |
| ASSERT_TRUE(new_loop2 != NULL); |
| |
| TestLoopFn original_loop_fn = reinterpret_cast<TestLoopFn>(original_loop); |
| original_loop_fn(5); |
| EXPECT_EQ(0, jitted_counter); |
| |
| TestLoopFn new_loop_fn = reinterpret_cast<TestLoopFn>(new_loop); |
| new_loop_fn(5); |
| EXPECT_EQ(5, jitted_counter); |
| new_loop_fn(5); |
| EXPECT_EQ(10, jitted_counter); |
| |
| TestLoopFn new_loop_fn2 = reinterpret_cast<TestLoopFn>(new_loop2); |
| new_loop_fn2(5); |
| EXPECT_EQ(0, jitted_counter); |
| codegen->Close(); |
| } |
| |
| // Test function for c++/ir interop for strings. Function will do: |
| // int StringTest(StringValue* strval) { |
| // strval->ptr[0] = 'A'; |
| // int len = strval->len; |
| // strval->len = 1; |
| // return len; |
| // } |
| // Corresponding IR is: |
| // define i32 @StringTest(%StringValue* %str) { |
| // entry: |
| // %str_ptr = getelementptr inbounds %StringValue* %str, i32 0, i32 0 |
| // %ptr = load i8** %str_ptr |
| // %first_char_ptr = getelementptr i8* %ptr, i32 0 |
| // store i8 65, i8* %first_char_ptr |
| // %len_ptr = getelementptr inbounds %StringValue* %str, i32 0, i32 1 |
| // %len = load i32* %len_ptr |
| // store i32 1, i32* %len_ptr |
| // ret i32 %len |
| // } |
| llvm::Function* CodegenStringTest(LlvmCodeGen* codegen) { |
| llvm::PointerType* string_val_ptr_type = |
| codegen->GetSlotPtrType(TYPE_STRING); |
| EXPECT_TRUE(string_val_ptr_type != NULL); |
| |
| LlvmCodeGen::FnPrototype prototype(codegen, "StringTest", codegen->i32_type()); |
| prototype.AddArgument(LlvmCodeGen::NamedVariable("str", string_val_ptr_type)); |
| LlvmBuilder builder(codegen->context()); |
| |
| llvm::Value* str; |
| llvm::Function* interop_fn = prototype.GeneratePrototype(&builder, &str); |
| |
| // strval->ptr[0] = 'A' |
| llvm::Value* str_ptr = builder.CreateStructGEP(NULL, str, 0, "str_ptr"); |
| llvm::Value* ptr = builder.CreateLoad(str_ptr, "ptr"); |
| llvm::Value* first_char_offset[] = {codegen->GetI32Constant(0)}; |
| llvm::Value* first_char_ptr = |
| builder.CreateGEP(ptr, first_char_offset, "first_char_ptr"); |
| builder.CreateStore(codegen->GetI8Constant('A'), first_char_ptr); |
| |
| // Update and return old len |
| llvm::Value* len_ptr = builder.CreateStructGEP(NULL, str, 1, "len_ptr"); |
| llvm::Value* len = builder.CreateLoad(len_ptr, "len"); |
| builder.CreateStore(codegen->GetI32Constant(1), len_ptr); |
| builder.CreateRet(len); |
| |
| return codegen->FinalizeFunction(interop_fn); |
| } |
| |
| // This test validates that the llvm StringValue struct matches the c++ stringvalue |
| // struct. Just create a simple StringValue struct and make sure the IR can read it |
| // and modify it. |
| TEST_F(LlvmCodeGenTest, StringValue) { |
| scoped_ptr<LlvmCodeGen> codegen; |
| ASSERT_OK(LlvmCodeGen::CreateImpalaCodegen(runtime_state_, NULL, "test", &codegen)); |
| EXPECT_TRUE(codegen.get() != NULL); |
| |
| string str("Test"); |
| |
| StringValue str_val; |
| // Call memset to make sure padding bits are zero. |
| memset(&str_val, 0, sizeof(str_val)); |
| str_val.ptr = const_cast<char*>(str.c_str()); |
| str_val.len = str.length(); |
| |
| llvm::Function* string_test_fn = CodegenStringTest(codegen.get()); |
| EXPECT_TRUE(string_test_fn != NULL); |
| |
| // Jit compile function |
| void* jitted_fn = NULL; |
| AddFunctionToJit(codegen.get(), string_test_fn, &jitted_fn); |
| ASSERT_OK(LlvmCodeGenTest::FinalizeModule(codegen.get())); |
| ASSERT_TRUE(jitted_fn != NULL); |
| |
| // Call IR function |
| typedef int (*TestStringInteropFn)(StringValue*); |
| TestStringInteropFn fn = reinterpret_cast<TestStringInteropFn>(jitted_fn); |
| int result = fn(&str_val); |
| |
| // Validate |
| EXPECT_EQ(str.length(), result); |
| EXPECT_EQ('A', str_val.ptr[0]); |
| EXPECT_EQ(1, str_val.len); |
| EXPECT_EQ(static_cast<void*>(str_val.ptr), static_cast<const void*>(str.c_str())); |
| |
| // After IMPALA-7367 removed the padding from the StringValue struct, validate the |
| // length byte alone. |
| int32_t* bytes = reinterpret_cast<int32_t*>(&str_val); |
| EXPECT_EQ(1, bytes[2]); // str_val.len |
| codegen->Close(); |
| } |
| |
| // Test calling memcpy intrinsic |
| TEST_F(LlvmCodeGenTest, MemcpyTest) { |
| scoped_ptr<LlvmCodeGen> codegen; |
| ASSERT_OK(LlvmCodeGen::CreateImpalaCodegen(runtime_state_, NULL, "test", &codegen)); |
| ASSERT_TRUE(codegen.get() != NULL); |
| |
| LlvmCodeGen::FnPrototype prototype(codegen.get(), "MemcpyTest", codegen->void_type()); |
| prototype.AddArgument(LlvmCodeGen::NamedVariable("dest", codegen->ptr_type())); |
| prototype.AddArgument(LlvmCodeGen::NamedVariable("src", codegen->ptr_type())); |
| prototype.AddArgument(LlvmCodeGen::NamedVariable("n", codegen->i32_type())); |
| |
| LlvmBuilder builder(codegen->context()); |
| |
| char src[] = "abcd"; |
| char dst[] = "aaaa"; |
| |
| llvm::Value* args[3]; |
| llvm::Function* fn = prototype.GeneratePrototype(&builder, &args[0]); |
| codegen->CodegenMemcpy(&builder, args[0], args[1], sizeof(src)); |
| builder.CreateRetVoid(); |
| |
| fn = codegen->FinalizeFunction(fn); |
| ASSERT_TRUE(fn != NULL); |
| |
| void* jitted_fn = NULL; |
| LlvmCodeGenTest::AddFunctionToJit(codegen.get(), fn, &jitted_fn); |
| ASSERT_OK(LlvmCodeGenTest::FinalizeModule(codegen.get())); |
| ASSERT_TRUE(jitted_fn != NULL); |
| |
| typedef void (*TestMemcpyFn)(char*, char*, int64_t); |
| TestMemcpyFn test_fn = reinterpret_cast<TestMemcpyFn>(jitted_fn); |
| |
| test_fn(dst, src, 4); |
| |
| EXPECT_EQ(memcmp(src, dst, 4), 0); |
| codegen->Close(); |
| } |
| |
| // Test codegen for hash |
| TEST_F(LlvmCodeGenTest, HashTest) { |
| // Values to compute hash on |
| const char* data1 = "test string"; |
| const char* data2 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| |
| bool restore_sse_support = false; |
| |
| // Loop to test both the sse4 on/off paths |
| for (int i = 0; i < 2; ++i) { |
| scoped_ptr<LlvmCodeGen> codegen; |
| ASSERT_OK(LlvmCodeGen::CreateImpalaCodegen(runtime_state_, NULL, "test", &codegen)); |
| ASSERT_TRUE(codegen.get() != NULL); |
| const auto close_codegen = |
| MakeScopeExitTrigger([&codegen]() { codegen->Close(); }); |
| |
| llvm::Value* llvm_data1 = |
| codegen->CastPtrToLlvmPtr(codegen->ptr_type(), const_cast<char*>(data1)); |
| llvm::Value* llvm_data2 = |
| codegen->CastPtrToLlvmPtr(codegen->ptr_type(), const_cast<char*>(data2)); |
| llvm::Value* llvm_len1 = codegen->GetI32Constant(strlen(data1)); |
| llvm::Value* llvm_len2 = codegen->GetI32Constant(strlen(data2)); |
| |
| uint32_t expected_hash = 0; |
| expected_hash = HashUtil::Hash(data1, strlen(data1), expected_hash); |
| expected_hash = HashUtil::Hash(data2, strlen(data2), expected_hash); |
| expected_hash = HashUtil::Hash(data1, strlen(data1), expected_hash); |
| |
| // Create a codegen'd function that hashes all the types and returns the results. |
| // The tuple/values to hash are baked into the codegen for simplicity. |
| LlvmCodeGen::FnPrototype prototype( |
| codegen.get(), "HashTest", codegen->i32_type()); |
| LlvmBuilder builder(codegen->context()); |
| |
| // Test both byte-size specific hash functions and the generic loop hash function |
| llvm::Function* fn_fixed = prototype.GeneratePrototype(&builder, NULL); |
| llvm::Function* data1_hash_fn = codegen->GetHashFunction(strlen(data1)); |
| llvm::Function* data2_hash_fn = codegen->GetHashFunction(strlen(data2)); |
| llvm::Function* generic_hash_fn = codegen->GetHashFunction(); |
| |
| ASSERT_TRUE(data1_hash_fn != NULL); |
| ASSERT_TRUE(data2_hash_fn != NULL); |
| ASSERT_TRUE(generic_hash_fn != NULL); |
| |
| llvm::Value* seed = codegen->GetI32Constant(0); |
| seed = builder.CreateCall( |
| data1_hash_fn, llvm::ArrayRef<llvm::Value*>({llvm_data1, llvm_len1, seed})); |
| seed = builder.CreateCall( |
| data2_hash_fn, llvm::ArrayRef<llvm::Value*>({llvm_data2, llvm_len2, seed})); |
| seed = builder.CreateCall( |
| generic_hash_fn, llvm::ArrayRef<llvm::Value*>({llvm_data1, llvm_len1, seed})); |
| builder.CreateRet(seed); |
| |
| fn_fixed = codegen->FinalizeFunction(fn_fixed); |
| ASSERT_TRUE(fn_fixed != NULL); |
| |
| void* jitted_fn = NULL; |
| LlvmCodeGenTest::AddFunctionToJit(codegen.get(), fn_fixed, &jitted_fn); |
| ASSERT_OK(LlvmCodeGenTest::FinalizeModule(codegen.get())); |
| ASSERT_TRUE(jitted_fn != NULL); |
| |
| typedef uint32_t (*TestHashFn)(); |
| TestHashFn test_fn = reinterpret_cast<TestHashFn>(jitted_fn); |
| |
| uint32_t result = test_fn(); |
| |
| // Validate that the hashes are identical |
| EXPECT_EQ(result, expected_hash) << LlvmCodeGen::IsCPUFeatureEnabled(CpuInfo::SSE4_2); |
| |
| if (i == 0 && LlvmCodeGen::IsCPUFeatureEnabled(CpuInfo::SSE4_2)) { |
| // Modify both CpuInfo and LlvmCodeGen::cpu_attrs_ to ensure that they have the |
| // same view of the underlying hardware while generating the hash. |
| EnableCodeGenCPUFeature(CpuInfo::SSE4_2, false); |
| CpuInfo::EnableFeature(CpuInfo::SSE4_2, false); |
| restore_sse_support = true; |
| LlvmCodeGenTest::ClearHashFns(codegen.get()); |
| } else { |
| // System doesn't have sse, no reason to test non-sse path again. |
| break; |
| } |
| } |
| |
| // Restore hardware feature for next test |
| EnableCodeGenCPUFeature(CpuInfo::SSE4_2, restore_sse_support); |
| CpuInfo::EnableFeature(CpuInfo::SSE4_2, restore_sse_support); |
| } |
| |
| // Test that an error propagating through codegen's diagnostic handler is |
| // captured by impala. An error is induced by asking Llvm to link the same lib twice. |
| TEST_F(LlvmCodeGenTest, HandleLinkageError) { |
| string ir_file_path("llvm-ir/test-loop.bc"); |
| string module_file; |
| PathBuilder::GetFullPath(ir_file_path, &module_file); |
| scoped_ptr<LlvmCodeGen> codegen; |
| ASSERT_OK(CreateFromFile(module_file.c_str(), &codegen)); |
| EXPECT_TRUE(codegen.get() != nullptr); |
| Status status = LinkModuleFromLocalFs(codegen.get(), module_file); |
| EXPECT_STR_CONTAINS(status.GetDetail(), "symbol multiply defined"); |
| codegen->Close(); |
| } |
| |
| // Test that the default whitelisting disables the expected attributes. |
| TEST_F(LlvmCodeGenTest, CpuAttrWhitelist) { |
| // Non-existent attributes should be disabled regardless of initial states. |
| // Whitelisted attributes like sse2 and lzcnt should retain their initial |
| // state. |
| EXPECT_EQ(std::unordered_set<string>( |
| {"-dummy1", "-dummy2", "-dummy3", "-dummy4", "+sse2", "-lzcnt"}), |
| LlvmCodeGen::ApplyCpuAttrWhitelist( |
| {"+dummy1", "+dummy2", "-dummy3", "+dummy4", "+sse2", "-lzcnt"})); |
| |
| // IMPALA-6291: Test that all AVX512 attributes are disabled. |
| vector<string> avx512_attrs; |
| EXPECT_EQ(std::unordered_set<string>({"-avx512ifma", "-avx512dqavx512er", "-avx512f", |
| "-avx512bw", "-avx512vl", "-avx512cd", "-avx512vbmi", "-avx512pf"}), |
| LlvmCodeGen::ApplyCpuAttrWhitelist({"+avx512ifma", "+avx512dqavx512er", "+avx512f", |
| "+avx512bw", "+avx512vl", "+avx512cd", "+avx512vbmi", "+avx512pf"})); |
| } |
| |
| // Test that exercises the code path that deletes non-finalized methods before it |
| // finalizes the llvm module. |
| TEST_F(LlvmCodeGenTest, CleanupNonFinalizedMethodsTest) { |
| scoped_ptr<LlvmCodeGen> codegen; |
| ASSERT_OK(LlvmCodeGen::CreateImpalaCodegen(runtime_state_, nullptr, "test", &codegen)); |
| ASSERT_TRUE(codegen.get() != nullptr); |
| const auto close_codegen = MakeScopeExitTrigger([&codegen]() { codegen->Close(); }); |
| LlvmBuilder builder(codegen->context()); |
| LlvmCodeGen::FnPrototype incomplete_prototype( |
| codegen.get(), "IncompleteFn", codegen->void_type()); |
| LlvmCodeGen::FnPrototype complete_prototype( |
| codegen.get(), "CompleteFn", codegen->void_type()); |
| llvm::Function* incomplete_fn = |
| incomplete_prototype.GeneratePrototype(&builder, nullptr); |
| llvm::Function* complete_fn = complete_prototype.GeneratePrototype(&builder, nullptr); |
| builder.CreateRetVoid(); |
| complete_fn = codegen->FinalizeFunction(complete_fn); |
| EXPECT_TRUE(complete_fn != nullptr); |
| ASSERT_TRUE(ContainsHandcraftedFn(codegen.get(), incomplete_fn)); |
| ASSERT_TRUE(ContainsHandcraftedFn(codegen.get(), complete_fn)); |
| ASSERT_OK(FinalizeModule(codegen.get())); |
| } |
| } |
| |
| int main(int argc, char **argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| impala::InitCommonRuntime(argc, argv, true, impala::TestInfo::BE_TEST); |
| impala::InitFeSupport(false); |
| ABORT_IF_ERROR(impala::LlvmCodeGen::InitializeLlvm()); |
| return RUN_ALL_TESTS(); |
| } |