1 // Copyright Contributors to the Open Shading Language project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4 
5 #include <OpenImageIO/typedesc.h>
6 #include <OpenImageIO/ustring.h>
7 
8 #include <OpenImageIO/argparse.h>
9 #include <OpenImageIO/strutil.h>
10 #include <OpenImageIO/sysutil.h>
11 #include <OpenImageIO/unittest.h>
12 #include <OSL/llvm_util.h>
13 
14 
15 typedef int (*IntFuncOfTwoInts)(int,int);
16 
17 static bool verbose = false;
18 static bool debug = false;
19 static int memtest = 0;
20 
21 
22 
23 static void
getargs(int argc,char * argv[])24 getargs (int argc, char *argv[])
25 {
26     bool help = false;
27     OIIO::ArgParse ap;
28     ap.options ("llvmutil_test\n"
29                 OIIO_INTRO_STRING "\n"
30                 "Usage:  llvmutil_test [options]",
31                 "--help", &help, "Print help message",
32                 "-v", &verbose, "Verbose mode",
33                 "--debug", &debug, "Debug mode",
34                 "--memtest %d", &memtest, "Memory test mode (arg: iterations)",
35                 // "--iters %d", &iterations,
36                 //     Strutil::sprintf("Number of iterations (default: %d)", iterations).c_str(),
37                 // "--trials %d", &ntrials, "Number of trials",
38                 NULL);
39     if (ap.parse (argc, (const char**)argv) < 0) {
40         std::cerr << ap.geterror() << std::endl;
41         ap.usage ();
42         exit (EXIT_FAILURE);
43     }
44     if (help) {
45         ap.usage ();
46         exit (EXIT_FAILURE);
47     }
48 }
49 
50 
51 
52 // This demonstrates the use of LLVM_Util to generate the following
53 // function on the fly, JIT it, and call it:
54 //      int myadd (int arg1, int arg2)
55 //      {
56 //          return arg1 + arg2;
57 //      }
58 //
59 void
test_int_func()60 test_int_func ()
61 {
62     // Setup
63     OSL::pvt::LLVM_Util::PerThreadInfo  pti;
64     OSL::pvt::LLVM_Util ll(pti);
65 
66     // Make a function with prototype:   int myadd (int arg1, int arg2)
67     // and make it the current function.
68     llvm::Function *func = ll.make_function ("myadd",         // name
69                                              false,           // fastcall
70                                              ll.type_int(),   // return
71                                              ll.type_int(),   // arg1
72                                              ll.type_int());  // arg2
73     ll.current_function (func);
74 
75     // Generate the ops for this function:  return arg1 + arg2
76     llvm::Value *arg1 = ll.current_function_arg (0);
77     llvm::Value *arg2 = ll.current_function_arg (1);
78     llvm::Value *sum = ll.op_add (arg1, arg2);
79     ll.op_return (sum);
80 
81     // Optimize it
82     ll.setup_optimization_passes (0);
83     ll.do_optimize ();
84 
85     // Print the optimized bitcode
86     std::cout << "Generated the following bitcode:\n"
87               << ll.bitcode_string(func) << "\n";
88 
89     // Ask for a callable function (will JIT on demand)
90     IntFuncOfTwoInts myadd = (IntFuncOfTwoInts) ll.getPointerToFunction (func);
91 
92     // Call it:
93     int result = myadd (13, 29);
94     std::cout << "The result is " << result << "\n";
95     OIIO_CHECK_EQUAL (result, 42);
96 }
97 
98 
99 
100 // This demonstrates the use of LLVM_Util to generate the following
101 // function on the fly, JIT it, and call it:
102 //      void myaddv (Vec3 *result, Vec3 *a, float b)
103 //      {
104 //          *result = (*a) * b;
105 //      }
106 //
107 void
test_triple_func()108 test_triple_func ()
109 {
110     // Setup
111     OSL::pvt::LLVM_Util::PerThreadInfo pti;
112     OSL::pvt::LLVM_Util ll(pti);
113 
114     // Make a function with prototype:   int myadd (int arg1, int arg2)
115     // and make it the current function.
116     llvm::Function *func = ll.make_function ("myaddv",        // name
117                                              false,           // fastcall
118                                              ll.type_void(),  // return
119                                              (llvm::Type *)ll.type_triple_ptr(), // result
120                                              (llvm::Type *)ll.type_triple_ptr(), // arg1
121                                              ll.type_float());  // arg2
122     ll.current_function (func);
123 
124     // Generate the ops for this function:  r = a*b
125     llvm::Value *rptr = ll.current_function_arg (0);
126     llvm::Value *aptr = ll.current_function_arg (1);
127     llvm::Value *b = ll.current_function_arg (2);
128     for (int i = 0; i < 3; ++i) {
129         llvm::Value *r_elptr = ll.GEP (rptr, 0, i);
130         llvm::Value *a_elptr = ll.GEP (aptr, 0, i);
131         llvm::Value *product = ll.op_mul (ll.op_load(a_elptr), b);
132         ll.op_store (product, r_elptr);
133     }
134     ll.op_return ();
135 
136     // Optimize it
137     ll.setup_optimization_passes (0);
138     ll.do_optimize ();
139 
140     // Print the optimized bitcode
141     std::cout << "Generated the following bitcode:\n"
142               << ll.bitcode_string(func) << "\n";
143 
144     // Ask for a callable function (will JIT on demand)
145     typedef void (*FuncVecVecFloat)(void*, void*, float);
146     FuncVecVecFloat f = (FuncVecVecFloat) ll.getPointerToFunction (func);
147 
148     // Call it:
149     {
150     float r[3], a[3] = { 1.0, 2.0, 3.0 }, b = 42.0;
151     f (r, a, b);
152     std::cout << "The result is " << r[0] << ' ' << r[1] << ' ' << r[2] << "\n";
153     OIIO_CHECK_EQUAL (r[0], 42.0);
154     OIIO_CHECK_EQUAL (r[1], 84.0);
155     OIIO_CHECK_EQUAL (r[2], 126.0);
156     }
157 }
158 
159 
160 
161 
162 // Make a crazy big function with lots of IR, having prototype:
163 //      int mybig (int arg1, int arg2);
164 //
165 IntFuncOfTwoInts
test_big_func(bool do_print=false)166 test_big_func (bool do_print=false)
167 {
168     // Setup
169     OSL::pvt::LLVM_Util::PerThreadInfo pti;
170     OSL::pvt::LLVM_Util ll(pti);
171 
172     // Make a function with prototype:  int myadd (int arg1, int arg2)
173     // and make it the current function in the current module.
174     llvm::Function *func = ll.make_function ("myadd",         // name
175                                              false,           // fastcall
176                                              ll.type_int(),   // return
177                                              ll.type_int(),   // arg1
178                                              ll.type_int());  // arg2
179     // Make it the current function and get it ready to accept IR.
180     ll.current_function (func);
181 
182     // Generate the ops for this function:  return arg1 + arg2
183     llvm::Value *arg1 = ll.current_function_arg (0);
184     llvm::Value *arg2 = ll.current_function_arg (1);
185     llvm::Value *sum = ll.op_add (arg1, arg2);
186     // Additional useless assignments, to bloat code
187     for (int i = 0; i < 1000; ++i) {
188         sum = ll.op_add (arg1, arg2);
189     }
190     ll.op_return (sum);
191 
192     // Print the optimized bitcode
193     // if (do_print)
194     //     std::cout << "Generated the following bitcode:\n"
195     //               << ll.bitcode_string(func) << "\n";
196 
197     ll.setup_optimization_passes (0, true /*targetHost*/);
198     ll.do_optimize ();
199 
200     if (do_print)
201         std::cout << "After optimizing:\n"
202                   << ll.bitcode_string(func) << "\n";
203 
204     // Ask for a callable function (will JIT on demand)
205     IntFuncOfTwoInts myadd = (IntFuncOfTwoInts) ll.getPointerToFunction (func);
206 
207     // We're done with the module now
208     // ll.remove_module (module);
209 
210     // Return the function. The callable code should survive the destruction
211     // of the LLVM_Util and its resources!
212     return myadd;
213 }
214 
215 
216 
217 void
test_isa_features()218 test_isa_features()
219 {
220     OSL::pvt::LLVM_Util::PerThreadInfo pti;
221     OSL::pvt::LLVM_Util ll(pti);
222 
223     ll.detect_cpu_features();
224 
225     // Make sure it matches what OIIO's cpuid queries reveal
226     OIIO_CHECK_EQUAL(ll.supports_avx(), OIIO::cpu_has_avx());
227     OIIO_CHECK_EQUAL(ll.supports_avx2(), OIIO::cpu_has_avx2());
228 #if OSL_LLVM_VERSION >= 80
229     OIIO_CHECK_EQUAL(ll.supports_avx512f(), OIIO::cpu_has_avx512f());
230     // N.B. Skip this check because for LLVM < 8, we intentionally disable
231     // AVX512 support in order to avoid a performance problem, so we
232     // expect this to potentially not match.
233 #endif
234 }
235 
236 
237 
238 int
main(int argc,char * argv[])239 main (int argc, char *argv[])
240 {
241     getargs (argc, argv);
242 
243     // This dummy object is the "owner" of the memory holding JITed code.
244     // It must outlive any LLVM_Util (and its PerThreadInfo).
245     OSL::pvt::LLVM_Util::ScopedJitMemoryUser llvm_jit_memory_user;
246 
247     test_isa_features();
248 
249     // Test simple functions
250     test_int_func();
251     test_triple_func();
252 
253     if (memtest) {
254         for (int i = 0; i < memtest; ++i) {
255             IntFuncOfTwoInts f = test_big_func (i==0);
256             int r = f (42, 42);
257             OIIO_CHECK_EQUAL (r, 84);
258         }
259         std::cout << "After " << memtest << " stupid functions compiled:\n";
260         std::cout << "   RSS memory = "
261                   << OIIO::Strutil::memformat(OIIO::Sysutil::memory_used()) << "\n";
262     }
263 
264     return unit_test_failures;
265 }
266