Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
llvm-codegen-test.cc
Go to the documentation of this file.
1 // Copyright 2012 Cloudera Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <string>
16 #include <gtest/gtest.h>
17 #include <boost/thread/thread.hpp>
18 
19 #include "codegen/llvm-codegen.h"
20 #include "common/init.h"
21 #include "runtime/raw-value.h"
22 #include "util/cpu-info.h"
23 #include "util/hash-util.h"
24 #include "util/path-builder.h"
25 
26 #include "common/names.h"
27 
28 using namespace llvm;
29 
30 namespace impala {
31 
32 class LlvmCodeGenTest : public testing:: Test {
33  protected:
34  static void LifetimeTest() {
36  Status status;
37  for (int i = 0; i < 10; ++i) {
38  LlvmCodeGen object1(&pool, "Test");
39  LlvmCodeGen object2(&pool, "Test");
40  LlvmCodeGen object3(&pool, "Test");
41 
42  status = object1.Init();
43  ASSERT_TRUE(status.ok());
44 
45  status = object2.Init();
46  ASSERT_TRUE(status.ok());
47 
48  status = object3.Init();
49  ASSERT_TRUE(status.ok());
50  }
51  }
52 
53  // Wrapper to call private test-only methods on LlvmCodeGen object
54  static Status LoadFromFile(ObjectPool* pool, const string& filename,
55  scoped_ptr<LlvmCodeGen>* codegen) {
56  return LlvmCodeGen::LoadFromFile(pool, filename, "test", codegen);
57  }
58 
60  LlvmCodeGen* codegen = pool->Add(new LlvmCodeGen(pool, "Test"));
61  if (codegen != NULL) {
62  Status status = codegen->Init();
63  if (!status.ok()) return NULL;
64  }
65  return codegen;
66  }
67 
68  static void ClearHashFns(LlvmCodeGen* codegen) {
69  codegen->ClearHashFns();
70  }
71 
72  static void* JitFunction(
73  LlvmCodeGen* codegen, Function* function) {
74  return codegen->JitFunction(function);
75  }
76 };
77 
78 // Simple test to just make and destroy llvmcodegen objects. LLVM
79 // has non-obvious object ownership transfers and this sanity checks that.
80 TEST_F(LlvmCodeGenTest, BasicLifetime) {
81  LifetimeTest();
82 }
83 
84 // Same as above but multithreaded
85 TEST_F(LlvmCodeGenTest, MultithreadedLifetime) {
86  const int NUM_THREADS = 10;
87  thread_group thread_group;
88  for (int i = 0; i < NUM_THREADS; ++i) {
89  thread_group.add_thread(new thread(&LifetimeTest));
90  }
91  thread_group.join_all();
92 }
93 
94 // Test loading a non-existent file
95 TEST_F(LlvmCodeGenTest, BadIRFile) {
97  string module_file = "NonExistentFile.ir";
98  scoped_ptr<LlvmCodeGen> codegen;
99  Status status = LlvmCodeGenTest::LoadFromFile(&pool, module_file.c_str(), &codegen);
100  EXPECT_TRUE(!status.ok());
101 }
102 
103 // IR for the generated linner loop
104 // define void @JittedInnerLoop() {
105 // entry:
106 // call void @DebugTrace(i8* inttoptr (i64 18970856 to i8*))
107 // %0 = load i64* inttoptr (i64 140735197627800 to i64*)
108 // %1 = add i64 %0, <delta>
109 // store i64 %1, i64* inttoptr (i64 140735197627800 to i64*)
110 // ret void
111 // }
112 // The random int in there is the address of jitted_counter
113 Function* CodegenInnerLoop(LlvmCodeGen* codegen, int64_t* jitted_counter, int delta) {
114  LLVMContext& context = codegen->context();
115  LlvmCodeGen::LlvmBuilder builder(context);
116 
117  LlvmCodeGen::FnPrototype fn_prototype(codegen, "JittedInnerLoop", codegen->void_type());
118  Function* jitted_loop_call = fn_prototype.GeneratePrototype();
119  BasicBlock* entry_block = BasicBlock::Create(context, "entry", jitted_loop_call);
120  builder.SetInsertPoint(entry_block);
121  codegen->CodegenDebugTrace(&builder, "Jitted");
122 
123  // Store &jitted_counter as a constant.
124  Value* const_delta = ConstantInt::get(context, APInt(64, delta));
125  Value* counter_ptr = codegen->CastPtrToLlvmPtr(codegen->GetPtrType(TYPE_BIGINT),
126  jitted_counter);
127  Value* loaded_counter = builder.CreateLoad(counter_ptr);
128  Value* incremented_value = builder.CreateAdd(loaded_counter, const_delta);
129  builder.CreateStore(incremented_value, counter_ptr);
130  builder.CreateRetVoid();
131 
132  return jitted_loop_call;
133 }
134 
135 // This test loads a precompiled IR file (compiled from testdata/llvm/test-loop.cc).
136 // The test contains two functions, an outer loop function and an inner loop function.
137 // The outer loop calls the inner loop function.
138 // The test will
139 // 1. create a LlvmCodegen object from the precompiled file
140 // 2. add another function to the module with the same signature as the inner
141 // loop function.
142 // 3. Replace the call instruction in the outer loop to a call to the new inner loop
143 // function.
144 // 4. Run the loop and make sure the inner loop is called.
145 // 5. Updated the jitted loop in place with another jitted inner loop function
146 // 6. Run the loop and make sure the updated is called.
147 TEST_F(LlvmCodeGenTest, ReplaceFnCall) {
149  const char* loop_call_name = "DefaultImplementation";
150  const char* loop_name = "TestLoop";
151  typedef void (*TestLoopFn)(int);
152 
153  string module_file;
154  PathBuilder::GetFullPath("llvm-ir/test-loop.ir", &module_file);
155 
156  // Part 1: Load the module and make sure everything is loaded correctly.
157  scoped_ptr<LlvmCodeGen> codegen;
158  Status status = LlvmCodeGenTest::LoadFromFile(&pool, module_file.c_str(), &codegen);
159  EXPECT_TRUE(codegen.get() != NULL);
160  EXPECT_TRUE(status.ok());
161 
162  vector<Function*> functions;
163  codegen->GetFunctions(&functions);
164  EXPECT_EQ(functions.size(), 2);
165 
166  Function* loop_call = functions[0];
167  Function* loop = functions[1];
168 
169  EXPECT_TRUE(loop_call->getName().find(loop_call_name) != string::npos);
170  EXPECT_TRUE(loop_call->arg_empty());
171  EXPECT_TRUE(loop->getName().find(loop_name) != string::npos);
172  EXPECT_EQ(loop->arg_size(), 1);
173 
174  void* original_loop = LlvmCodeGenTest::JitFunction(codegen.get(), loop);
175  EXPECT_TRUE(original_loop != NULL);
176 
177  TestLoopFn original_loop_fn = reinterpret_cast<TestLoopFn>(original_loop);
178  original_loop_fn(5);
179 
180  // Part 2: Generate a new inner loop function.
181  //
182  // The c++ version of the code is
183  // static int64_t* counter;
184  // void JittedInnerLoop() {
185  // printf("LLVM Trace: Jitted\n");
186  // ++*counter;
187  // }
188  //
189  int64_t jitted_counter = 0;
190  Function* jitted_loop_call = CodegenInnerLoop(codegen.get(), &jitted_counter, 1);
191 
192  // Part 3: Replace the call instruction to the normal function with a call to the
193  // jitted one
194  int num_replaced;
195  Function* jitted_loop = codegen->ReplaceCallSites(
196  loop, false, jitted_loop_call, loop_call_name, &num_replaced);
197  EXPECT_EQ(num_replaced, 1);
198  EXPECT_TRUE(codegen->VerifyFunction(jitted_loop));
199 
200  // Part4: Call the new loop and verify results
201  void* new_loop = LlvmCodeGenTest::JitFunction(codegen.get(), jitted_loop);
202  EXPECT_TRUE(new_loop != NULL);
203 
204  TestLoopFn new_loop_fn = reinterpret_cast<TestLoopFn>(new_loop);
205  EXPECT_EQ(jitted_counter, 0);
206  new_loop_fn(5);
207  EXPECT_EQ(jitted_counter, 5);
208  new_loop_fn(5);
209  EXPECT_EQ(jitted_counter, 10);
210 
211  // Part5: Generate a new inner loop function and a new loop function in place
212  Function* jitted_loop_call2 = CodegenInnerLoop(codegen.get(), &jitted_counter, -2);
213  Function* jitted_loop2 = codegen->ReplaceCallSites(loop, true, jitted_loop_call2,
214  loop_call_name, &num_replaced);
215  EXPECT_EQ(num_replaced, 1);
216  EXPECT_TRUE(codegen->VerifyFunction(jitted_loop2));
217 
218  // Part6: Call new loop
219  void* new_loop2 =
220  LlvmCodeGenTest::JitFunction(codegen.get(), jitted_loop2);
221  EXPECT_TRUE(new_loop2 != NULL);
222 
223  TestLoopFn new_loop_fn2 = reinterpret_cast<TestLoopFn>(new_loop2);
224  new_loop_fn2(5);
225  EXPECT_EQ(jitted_counter, 0);
226 }
227 
228 // Test function for c++/ir interop for strings. Function will do:
229 // int StringTest(StringValue* strval) {
230 // strval->ptr[0] = 'A';
231 // int len = strval->len;
232 // strval->len = 1;
233 // return len;
234 // }
235 // Corresponding IR is:
236 // define i32 @StringTest(%StringValue* %str) {
237 // entry:
238 // %str_ptr = getelementptr inbounds %StringValue* %str, i32 0, i32 0
239 // %ptr = load i8** %str_ptr
240 // %first_char_ptr = getelementptr i8* %ptr, i32 0
241 // store i8 65, i8* %first_char_ptr
242 // %len_ptr = getelementptr inbounds %StringValue* %str, i32 0, i32 1
243 // %len = load i32* %len_ptr
244 // store i32 1, i32* %len_ptr
245 // ret i32 %len
246 // }
247 Function* CodegenStringTest(LlvmCodeGen* codegen) {
248  PointerType* string_val_ptr_type = codegen->GetPtrType(TYPE_STRING);
249  EXPECT_TRUE(string_val_ptr_type != NULL);
250 
251  LlvmCodeGen::FnPrototype prototype(codegen, "StringTest", codegen->GetType(TYPE_INT));
252  prototype.AddArgument(LlvmCodeGen::NamedVariable("str", string_val_ptr_type));
253  LlvmCodeGen::LlvmBuilder builder(codegen->context());
254 
255  Value* str;
256  Function* interop_fn = prototype.GeneratePrototype(&builder, &str);
257 
258  // strval->ptr[0] = 'A'
259  Value* str_ptr = builder.CreateStructGEP(str, 0, "str_ptr");
260  Value* ptr = builder.CreateLoad(str_ptr, "ptr");
261  Value* first_char_offset[] = { codegen->GetIntConstant(TYPE_INT, 0) };
262  Value* first_char_ptr = builder.CreateGEP(ptr, first_char_offset, "first_char_ptr");
263  builder.CreateStore(codegen->GetIntConstant(TYPE_TINYINT, 'A'), first_char_ptr);
264 
265  // Update and return old len
266  Value* len_ptr = builder.CreateStructGEP(str, 1, "len_ptr");
267  Value* len = builder.CreateLoad(len_ptr, "len");
268  builder.CreateStore(codegen->GetIntConstant(TYPE_INT, 1), len_ptr);
269  builder.CreateRet(len);
270 
271  return interop_fn;
272 }
273 
274 // This test validates that the llvm StringValue struct matches the c++ stringvalue
275 // struct. Just create a simple StringValue struct and make sure the IR can read it
276 // and modify it.
279 
280  scoped_ptr<LlvmCodeGen> codegen;
281  Status status = LlvmCodeGen::LoadImpalaIR(&pool, "test", &codegen);
282  EXPECT_TRUE(status.ok());
283  EXPECT_TRUE(codegen.get() != NULL);
284 
285  string str("Test");
286 
287  StringValue str_val;
288  // Call memset to make sure padding bits are zero.
289  memset(&str_val, 0, sizeof(str_val));
290  str_val.ptr = const_cast<char*>(str.c_str());
291  str_val.len = str.length();
292 
293  Function* string_test_fn = CodegenStringTest(codegen.get());
294  EXPECT_TRUE(string_test_fn != NULL);
295  EXPECT_TRUE(codegen->VerifyFunction(string_test_fn));
296 
297  // Jit compile function
298  void* jitted_fn = LlvmCodeGenTest::JitFunction(codegen.get(), string_test_fn);
299  EXPECT_TRUE(jitted_fn != NULL);
300 
301  // Call IR function
302  typedef int (*TestStringInteropFn)(StringValue*);
303  TestStringInteropFn fn = reinterpret_cast<TestStringInteropFn>(jitted_fn);
304  int result = fn(&str_val);
305 
306  // Validate
307  EXPECT_EQ(str.length(), result);
308  EXPECT_EQ('A', str_val.ptr[0]);
309  EXPECT_EQ(1, str_val.len);
310  EXPECT_EQ(static_cast<void*>(str_val.ptr), static_cast<const void*>(str.c_str()));
311 
312  // Validate padding bytes are unchanged
313  int32_t* bytes = reinterpret_cast<int32_t*>(&str_val);
314  EXPECT_EQ(1, bytes[2]); // str_val.len
315  EXPECT_EQ(0, bytes[3]); // padding
316 }
317 
318 // Test calling memcpy intrinsic
319 TEST_F(LlvmCodeGenTest, MemcpyTest) {
321 
322  scoped_ptr<LlvmCodeGen> codegen;
323  Status status = LlvmCodeGen::LoadImpalaIR(&pool, "test", &codegen);
324  ASSERT_TRUE(status.ok());
325  ASSERT_TRUE(codegen.get() != NULL);
326 
327  LlvmCodeGen::FnPrototype prototype(codegen.get(), "MemcpyTest", codegen->void_type());
328  prototype.AddArgument(LlvmCodeGen::NamedVariable("dest", codegen->ptr_type()));
329  prototype.AddArgument(LlvmCodeGen::NamedVariable("src", codegen->ptr_type()));
330  prototype.AddArgument(LlvmCodeGen::NamedVariable("n", codegen->GetType(TYPE_INT)));
331 
332  LlvmCodeGen::LlvmBuilder builder(codegen->context());
333 
334  char src[] = "abcd";
335  char dst[] = "aaaa";
336 
337  Value* args[3];
338  Function* fn = prototype.GeneratePrototype(&builder, &args[0]);
339  codegen->CodegenMemcpy(&builder, args[0], args[1], sizeof(src));
340  builder.CreateRetVoid();
341 
342  fn = codegen->FinalizeFunction(fn);
343  ASSERT_TRUE(fn != NULL);
344 
345  void* jitted_fn = LlvmCodeGenTest::JitFunction(codegen.get(), fn);
346  ASSERT_TRUE(jitted_fn != NULL);
347 
348  typedef void (*TestMemcpyFn)(char*, char*, int64_t);
349  TestMemcpyFn test_fn = reinterpret_cast<TestMemcpyFn>(jitted_fn);
350 
351  test_fn(dst, src, 4);
352 
353  EXPECT_EQ(memcmp(src, dst, 4), 0);
354 }
355 
356 // Test codegen for hash
359 
360  // Values to compute hash on
361  const char* data1 = "test string";
362  const char* data2 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
363 
364  scoped_ptr<LlvmCodeGen> codegen;
365  Status status = LlvmCodeGen::LoadImpalaIR(&pool, "test", &codegen);
366  ASSERT_TRUE(status.ok());
367  ASSERT_TRUE(codegen.get() != NULL);
368 
369  bool restore_sse_support = false;
370 
371  Value* llvm_data1 = codegen->CastPtrToLlvmPtr(codegen->ptr_type(),
372  const_cast<char*>(data1));
373  Value* llvm_data2 = codegen->CastPtrToLlvmPtr(codegen->ptr_type(),
374  const_cast<char*>(data2));
375  Value* llvm_len1 = codegen->GetIntConstant(TYPE_INT, strlen(data1));
376  Value* llvm_len2 = codegen->GetIntConstant(TYPE_INT, strlen(data2));
377 
378  // Loop to test both the sse4 on/off paths
379  for (int i = 0; i < 2; ++i) {
380  uint32_t expected_hash = 0;
381  expected_hash = HashUtil::Hash(data1, strlen(data1), expected_hash);
382  expected_hash = HashUtil::Hash(data2, strlen(data2), expected_hash);
383  expected_hash = HashUtil::Hash(data1, strlen(data1), expected_hash);
384 
385  // Create a codegen'd function that hashes all the types and returns the results.
386  // The tuple/values to hash are baked into the codegen for simplicity.
387  LlvmCodeGen::FnPrototype prototype(codegen.get(), "HashTest",
388  codegen->GetType(TYPE_INT));
389  LlvmCodeGen::LlvmBuilder builder(codegen->context());
390 
391  // Test both byte-size specific hash functions and the generic loop hash function
392  Function* fn_fixed = prototype.GeneratePrototype(&builder, NULL);
393  Function* data1_hash_fn = codegen->GetHashFunction(strlen(data1));
394  Function* data2_hash_fn = codegen->GetHashFunction(strlen(data2));
395  Function* generic_hash_fn = codegen->GetHashFunction();
396 
397  ASSERT_TRUE(data1_hash_fn != NULL);
398  ASSERT_TRUE(data2_hash_fn != NULL);
399  ASSERT_TRUE(generic_hash_fn != NULL);
400 
401  Value* seed = codegen->GetIntConstant(TYPE_INT, 0);
402  seed = builder.CreateCall3(data1_hash_fn, llvm_data1, llvm_len1, seed);
403  seed = builder.CreateCall3(data2_hash_fn, llvm_data2, llvm_len2, seed);
404  seed = builder.CreateCall3(generic_hash_fn, llvm_data1, llvm_len1, seed);
405  builder.CreateRet(seed);
406 
407  fn_fixed = codegen->FinalizeFunction(fn_fixed);
408  ASSERT_TRUE(fn_fixed != NULL);
409 
410  void* jitted_fn = LlvmCodeGenTest::JitFunction(codegen.get(), fn_fixed);
411  ASSERT_TRUE(jitted_fn != NULL);
412 
413  typedef uint32_t (*TestHashFn)();
414  TestHashFn test_fn = reinterpret_cast<TestHashFn>(jitted_fn);
415 
416  uint32_t result = test_fn();
417 
418  // Validate that the hashes are identical
419  EXPECT_EQ(result, expected_hash) << CpuInfo::IsSupported(CpuInfo::SSE4_2);
420 
421  if (i == 0 && CpuInfo::IsSupported(CpuInfo::SSE4_2)) {
422  CpuInfo::EnableFeature(CpuInfo::SSE4_2, false);
423  restore_sse_support = true;
424  LlvmCodeGenTest::ClearHashFns(codegen.get());
425  } else {
426  // System doesn't have sse, no reason to test non-sse path again.
427  break;
428  }
429  }
430 
431  // Restore hardware feature for next test
432  CpuInfo::EnableFeature(CpuInfo::SSE4_2, restore_sse_support);
433 }
434 
435 }
436 
437 int main(int argc, char **argv) {
438  impala::InitCommonRuntime(argc, argv, false);
439  ::testing::InitGoogleTest(&argc, argv);
441  return RUN_ALL_TESTS();
442 }
llvm::PointerType * GetPtrType(llvm::Type *type)
Return a pointer type to 'type'.
Utility struct that wraps a variable name and llvm type.
Definition: llvm-codegen.h:149
Status Init()
Initializes the jitter and execution engine.
void InitCommonRuntime(int argc, char **argv, bool init_jvm, TestInfo::Mode m=TestInfo::NON_TEST)
Definition: init.cc:122
static void ClearHashFns(LlvmCodeGen *codegen)
static Status LoadFromFile(ObjectPool *pool, const string &filename, scoped_ptr< LlvmCodeGen > *codegen)
static void * JitFunction(LlvmCodeGen *codegen, Function *function)
Function * CodegenStringTest(LlvmCodeGen *codegen)
LLVM code generator. This is the top level object to generate jitted code.
Definition: llvm-codegen.h:107
int main(int argc, char **argv)
llvm::Function * GeneratePrototype(LlvmBuilder *builder=NULL, llvm::Value **params=NULL)
llvm::Value * CastPtrToLlvmPtr(llvm::Type *type, const void *ptr)
void AddArgument(const NamedVariable &var)
Add argument.
Definition: llvm-codegen.h:171
ObjectPool pool
void ClearHashFns()
Clears generated hash fns. This is only used for testing.
static uint64_t Hash(const IntVal &v)
static LlvmCodeGen * CreateCodegen(ObjectPool *pool)
uint64_t Test(T *ht, const ProbeTuple *input, uint64_t num_tuples)
static void InitializeLlvm(bool load_backend=false)
Definition: llvm-codegen.cc:78
llvm::Type * GetType(const ColumnType &type)
Returns llvm type for the column type.
llvm::Value * GetIntConstant(PrimitiveType type, int64_t val)
Returns the constant 'val' of 'type'.
TEST_F(LlvmCodeGenTest, HashTest)
void CodegenDebugTrace(LlvmBuilder *builder, const char *message)
bool ok() const
Definition: status.h:172
llvm::Type * void_type()
Definition: llvm-codegen.h:394
llvm::LLVMContext & context()
Definition: llvm-codegen.h:214
void * JitFunction(llvm::Function *function)
const int NUM_THREADS
Function * CodegenInnerLoop(LlvmCodeGen *codegen, int64_t *jitted_counter, int delta)