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