Mercurial > hg > CbC > CbC_llvm
comparison utils/shuffle_select_fuzz_tester.py @ 171:66f3bfe93da9
git version 2c4ca6832fa6b306ee6a7010bfb80a3f2596f824
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Mon, 25 May 2020 11:07:02 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
150:1d019706d866 | 171:66f3bfe93da9 |
---|---|
1 #!/usr/bin/env python | |
2 | |
3 """A shuffle-select vector fuzz tester. | |
4 | |
5 This is a python program to fuzz test the LLVM shufflevector and select | |
6 instructions. It generates a function with a random sequnece of shufflevectors | |
7 while optionally attaching it with a select instruction (regular or zero merge), | |
8 maintaining the element mapping accumulated across the function. It then | |
9 generates a main function which calls it with a different value in each element | |
10 and checks that the result matches the expected mapping. | |
11 | |
12 Take the output IR printed to stdout, compile it to an executable using whatever | |
13 set of transforms you want to test, and run the program. If it crashes, it found | |
14 a bug (an error message with the expected and actual result is printed). | |
15 """ | |
16 from __future__ import print_function | |
17 | |
18 import random | |
19 import uuid | |
20 import argparse | |
21 | |
22 # Possibility of one undef index in generated mask for shufflevector instruction | |
23 SHUF_UNDEF_POS = 0.15 | |
24 | |
25 # Possibility of one undef index in generated mask for select instruction | |
26 SEL_UNDEF_POS = 0.15 | |
27 | |
28 # Possibility of adding a select instruction to the result of a shufflevector | |
29 ADD_SEL_POS = 0.4 | |
30 | |
31 # If we are adding a select instruction, this is the possibility of a | |
32 # merge-select instruction (1 - MERGE_SEL_POS = possibility of zero-merge-select | |
33 # instruction. | |
34 MERGE_SEL_POS = 0.5 | |
35 | |
36 | |
37 test_template = r''' | |
38 define internal fastcc {ty} @test({inputs}) noinline nounwind {{ | |
39 entry: | |
40 {instructions} | |
41 ret {ty} {last_name} | |
42 }} | |
43 ''' | |
44 | |
45 error_template = r'''@error.{lane} = private unnamed_addr global [64 x i8] c"FAIL: lane {lane}, expected {exp}, found %d\0A{padding}"''' | |
46 | |
47 main_template = r''' | |
48 define i32 @main() {{ | |
49 entry: | |
50 ; Create a scratch space to print error messages. | |
51 %str = alloca [64 x i8] | |
52 %str.ptr = getelementptr inbounds [64 x i8], [64 x i8]* %str, i32 0, i32 0 | |
53 | |
54 ; Build the input vector and call the test function. | |
55 %v = call fastcc {ty} @test({inputs}) | |
56 br label %test.0 | |
57 | |
58 {check_die} | |
59 }} | |
60 | |
61 declare i32 @strlen(i8*) | |
62 declare i32 @write(i32, i8*, i32) | |
63 declare i32 @sprintf(i8*, i8*, ...) | |
64 declare void @llvm.trap() noreturn nounwind | |
65 ''' | |
66 | |
67 check_template = r''' | |
68 test.{lane}: | |
69 %v.{lane} = extractelement {ty} %v, i32 {lane} | |
70 %cmp.{lane} = {i_f}cmp {ordered}ne {scalar_ty} %v.{lane}, {exp} | |
71 br i1 %cmp.{lane}, label %die.{lane}, label %test.{n_lane} | |
72 ''' | |
73 | |
74 undef_check_template = r''' | |
75 test.{lane}: | |
76 ; Skip this lane, its value is undef. | |
77 br label %test.{n_lane} | |
78 ''' | |
79 | |
80 die_template = r''' | |
81 die.{lane}: | |
82 ; Capture the actual value and print an error message. | |
83 call i32 (i8*, i8*, ...) @sprintf(i8* %str.ptr, i8* getelementptr inbounds ([64 x i8], [64 x i8]* @error.{lane}, i32 0, i32 0), {scalar_ty} %v.{lane}) | |
84 %length.{lane} = call i32 @strlen(i8* %str.ptr) | |
85 call i32 @write(i32 2, i8* %str.ptr, i32 %length.{lane}) | |
86 call void @llvm.trap() | |
87 unreachable | |
88 ''' | |
89 | |
90 class Type: | |
91 def __init__(self, is_float, elt_width, elt_num): | |
92 self.is_float = is_float # Boolean | |
93 self.elt_width = elt_width # Integer | |
94 self.elt_num = elt_num # Integer | |
95 | |
96 def dump(self): | |
97 if self.is_float: | |
98 str_elt = 'float' if self.elt_width == 32 else 'double' | |
99 else: | |
100 str_elt = 'i' + str(self.elt_width) | |
101 | |
102 if self.elt_num == 1: | |
103 return str_elt | |
104 else: | |
105 return '<' + str(self.elt_num) + ' x ' + str_elt + '>' | |
106 | |
107 def get_scalar_type(self): | |
108 return Type(self.is_float, self.elt_width, 1) | |
109 | |
110 | |
111 | |
112 # Class to represent any value (variable) that can be used. | |
113 class Value: | |
114 def __init__(self, name, ty, value = None): | |
115 self.ty = ty # Type | |
116 self.name = name # String | |
117 self.value = value # list of integers or floating points | |
118 | |
119 | |
120 # Class to represent an IR instruction (shuffle/select). | |
121 class Instruction(Value): | |
122 def __init__(self, name, ty, op0, op1, mask): | |
123 Value.__init__(self, name, ty) | |
124 self.op0 = op0 # Value | |
125 self.op1 = op1 # Value | |
126 self.mask = mask # list of integers | |
127 | |
128 def dump(self): pass | |
129 | |
130 def calc_value(self): pass | |
131 | |
132 | |
133 # Class to represent an IR shuffle instruction | |
134 class ShufInstr(Instruction): | |
135 | |
136 shuf_template = ' {name} = shufflevector {ty} {op0}, {ty} {op1}, <{num} x i32> {mask}\n' | |
137 | |
138 def __init__(self, name, ty, op0, op1, mask): | |
139 Instruction.__init__(self, '%shuf' + name, ty, op0, op1, mask) | |
140 | |
141 def dump(self): | |
142 str_mask = [('i32 ' + str(idx)) if idx != -1 else 'i32 undef' for idx in self.mask] | |
143 str_mask = '<' + (', ').join(str_mask) + '>' | |
144 return self.shuf_template.format(name = self.name, ty = self.ty.dump(), op0 = self.op0.name, | |
145 op1 = self.op1.name, num = self.ty.elt_num, mask = str_mask) | |
146 | |
147 def calc_value(self): | |
148 if self.value != None: | |
149 print('Trying to calculate the value of a shuffle instruction twice') | |
150 exit(1) | |
151 | |
152 result = [] | |
153 for i in range(len(self.mask)): | |
154 index = self.mask[i] | |
155 | |
156 if index < self.ty.elt_num and index >= 0: | |
157 result.append(self.op0.value[index]) | |
158 elif index >= self.ty.elt_num: | |
159 index = index % self.ty.elt_num | |
160 result.append(self.op1.value[index]) | |
161 else: # -1 => undef | |
162 result.append(-1) | |
163 | |
164 self.value = result | |
165 | |
166 | |
167 # Class to represent an IR select instruction | |
168 class SelectInstr(Instruction): | |
169 | |
170 sel_template = ' {name} = select <{num} x i1> {mask}, {ty} {op0}, {ty} {op1}\n' | |
171 | |
172 def __init__(self, name, ty, op0, op1, mask): | |
173 Instruction.__init__(self, '%sel' + name, ty, op0, op1, mask) | |
174 | |
175 def dump(self): | |
176 str_mask = [('i1 ' + str(idx)) if idx != -1 else 'i1 undef' for idx in self.mask] | |
177 str_mask = '<' + (', ').join(str_mask) + '>' | |
178 return self.sel_template.format(name = self.name, ty = self.ty.dump(), op0 = self.op0.name, | |
179 op1 = self.op1.name, num = self.ty.elt_num, mask = str_mask) | |
180 | |
181 def calc_value(self): | |
182 if self.value != None: | |
183 print('Trying to calculate the value of a select instruction twice') | |
184 exit(1) | |
185 | |
186 result = [] | |
187 for i in range(len(self.mask)): | |
188 index = self.mask[i] | |
189 | |
190 if index == 1: | |
191 result.append(self.op0.value[i]) | |
192 elif index == 0: | |
193 result.append(self.op1.value[i]) | |
194 else: # -1 => undef | |
195 result.append(-1) | |
196 | |
197 self.value = result | |
198 | |
199 | |
200 # Returns a list of Values initialized with actual numbers according to the | |
201 # provided type | |
202 def gen_inputs(ty, num): | |
203 inputs = [] | |
204 for i in range(num): | |
205 inp = [] | |
206 for j in range(ty.elt_num): | |
207 if ty.is_float: | |
208 inp.append(float(i*ty.elt_num + j)) | |
209 else: | |
210 inp.append((i*ty.elt_num + j) % (1 << ty.elt_width)) | |
211 inputs.append(Value('%inp' + str(i), ty, inp)) | |
212 | |
213 return inputs | |
214 | |
215 | |
216 # Returns a random vector type to be tested | |
217 # In case one of the dimensions (scalar type/number of elements) is provided, | |
218 # fill the blank dimension and return appropriate Type object. | |
219 def get_random_type(ty, num_elts): | |
220 if ty != None: | |
221 if ty == 'i8': | |
222 is_float = False | |
223 width = 8 | |
224 elif ty == 'i16': | |
225 is_float = False | |
226 width = 16 | |
227 elif ty == 'i32': | |
228 is_float = False | |
229 width = 32 | |
230 elif ty == 'i64': | |
231 is_float = False | |
232 width = 64 | |
233 elif ty == 'f32': | |
234 is_float = True | |
235 width = 32 | |
236 elif ty == 'f64': | |
237 is_float = True | |
238 width = 64 | |
239 | |
240 int_elt_widths = [8, 16, 32, 64] | |
241 float_elt_widths = [32, 64] | |
242 | |
243 if num_elts == None: | |
244 num_elts = random.choice(range(2, 65)) | |
245 | |
246 if ty == None: | |
247 # 1 for integer type, 0 for floating-point | |
248 if random.randint(0,1): | |
249 is_float = False | |
250 width = random.choice(int_elt_widths) | |
251 else: | |
252 is_float = True | |
253 width = random.choice(float_elt_widths) | |
254 | |
255 return Type(is_float, width, num_elts) | |
256 | |
257 | |
258 # Generate mask for shufflevector IR instruction, with SHUF_UNDEF_POS possibility | |
259 # of one undef index. | |
260 def gen_shuf_mask(ty): | |
261 mask = [] | |
262 for i in range(ty.elt_num): | |
263 if SHUF_UNDEF_POS/ty.elt_num > random.random(): | |
264 mask.append(-1) | |
265 else: | |
266 mask.append(random.randint(0, ty.elt_num*2 - 1)) | |
267 | |
268 return mask | |
269 | |
270 | |
271 # Generate mask for select IR instruction, with SEL_UNDEF_POS possibility | |
272 # of one undef index. | |
273 def gen_sel_mask(ty): | |
274 mask = [] | |
275 for i in range(ty.elt_num): | |
276 if SEL_UNDEF_POS/ty.elt_num > random.random(): | |
277 mask.append(-1) | |
278 else: | |
279 mask.append(random.randint(0, 1)) | |
280 | |
281 return mask | |
282 | |
283 # Generate shuffle instructions with optional select instruction after. | |
284 def gen_insts(inputs, ty): | |
285 int_zero_init = Value('zeroinitializer', ty, [0]*ty.elt_num) | |
286 float_zero_init = Value('zeroinitializer', ty, [0.0]*ty.elt_num) | |
287 | |
288 insts = [] | |
289 name_idx = 0 | |
290 while len(inputs) > 1: | |
291 # Choose 2 available Values - remove them from inputs list. | |
292 [idx0, idx1] = sorted(random.sample(range(len(inputs)), 2)) | |
293 op0 = inputs[idx0] | |
294 op1 = inputs[idx1] | |
295 | |
296 # Create the shuffle instruction. | |
297 shuf_mask = gen_shuf_mask(ty) | |
298 shuf_inst = ShufInstr(str(name_idx), ty, op0, op1, shuf_mask) | |
299 shuf_inst.calc_value() | |
300 | |
301 # Add the new shuffle instruction to the list of instructions. | |
302 insts.append(shuf_inst) | |
303 | |
304 # Optionally, add select instruction with the result of the previous shuffle. | |
305 if random.random() < ADD_SEL_POS: | |
306 # Either blending with a random Value or with an all-zero vector. | |
307 if random.random() < MERGE_SEL_POS: | |
308 op2 = random.choice(inputs) | |
309 else: | |
310 op2 = float_zero_init if ty.is_float else int_zero_init | |
311 | |
312 select_mask = gen_sel_mask(ty) | |
313 select_inst = SelectInstr(str(name_idx), ty, shuf_inst, op2, select_mask) | |
314 select_inst.calc_value() | |
315 | |
316 # Add the select instructions to the list of instructions and to the available Values. | |
317 insts.append(select_inst) | |
318 inputs.append(select_inst) | |
319 else: | |
320 # If the shuffle instruction is not followed by select, add it to the available Values. | |
321 inputs.append(shuf_inst) | |
322 | |
323 del inputs[idx1] | |
324 del inputs[idx0] | |
325 name_idx += 1 | |
326 | |
327 return insts | |
328 | |
329 | |
330 def main(): | |
331 parser = argparse.ArgumentParser(description=__doc__) | |
332 parser.add_argument('--seed', default=str(uuid.uuid4()), | |
333 help='A string used to seed the RNG') | |
334 parser.add_argument('--max-num-inputs', type=int, default=20, | |
335 help='Specify the maximum number of vector inputs for the test. (default: 20)') | |
336 parser.add_argument('--min-num-inputs', type=int, default=10, | |
337 help='Specify the minimum number of vector inputs for the test. (default: 10)') | |
338 parser.add_argument('--type', default=None, | |
339 help=''' | |
340 Choose specific type to be tested. | |
341 i8, i16, i32, i64, f32 or f64. | |
342 (default: random)''') | |
343 parser.add_argument('--num-elts', default=None, type=int, | |
344 help='Choose specific number of vector elements to be tested. (default: random)') | |
345 args = parser.parse_args() | |
346 | |
347 print('; The seed used for this test is ' + args.seed) | |
348 | |
349 assert args.min_num_inputs < args.max_num_inputs , "Minimum value greater than maximum." | |
350 assert args.type in [None, 'i8', 'i16', 'i32', 'i64', 'f32', 'f64'], "Illegal type." | |
351 assert args.num_elts == None or args.num_elts > 0, "num_elts must be a positive integer." | |
352 | |
353 random.seed(args.seed) | |
354 ty = get_random_type(args.type, args.num_elts) | |
355 inputs = gen_inputs(ty, random.randint(args.min_num_inputs, args.max_num_inputs)) | |
356 inputs_str = (', ').join([inp.ty.dump() + ' ' + inp.name for inp in inputs]) | |
357 inputs_values = [inp.value for inp in inputs] | |
358 | |
359 insts = gen_insts(inputs, ty) | |
360 | |
361 assert len(inputs) == 1, "Only one value should be left after generating phase" | |
362 res = inputs[0] | |
363 | |
364 # print the actual test function by dumping the generated instructions. | |
365 insts_str = ''.join([inst.dump() for inst in insts]) | |
366 print(test_template.format(ty = ty.dump(), inputs = inputs_str, | |
367 instructions = insts_str, last_name = res.name)) | |
368 | |
369 # Print the error message templates as global strings | |
370 for i in range(len(res.value)): | |
371 pad = ''.join(['\\00']*(31 - len(str(i)) - len(str(res.value[i])))) | |
372 print(error_template.format(lane = str(i), exp = str(res.value[i]), | |
373 padding = pad)) | |
374 | |
375 # Prepare the runtime checks and failure handlers. | |
376 scalar_ty = ty.get_scalar_type() | |
377 check_die = '' | |
378 i_f = 'f' if ty.is_float else 'i' | |
379 ordered = 'o' if ty.is_float else '' | |
380 for i in range(len(res.value)): | |
381 if res.value[i] != -1: | |
382 # Emit runtime check for each non-undef expected value. | |
383 check_die += check_template.format(lane = str(i), n_lane = str(i+1), | |
384 ty = ty.dump(), i_f = i_f, scalar_ty = scalar_ty.dump(), | |
385 exp = str(res.value[i]), ordered = ordered) | |
386 # Emit failure handler for each runtime check with proper error message | |
387 check_die += die_template.format(lane = str(i), scalar_ty = scalar_ty.dump()) | |
388 else: | |
389 # Ignore lanes with undef result | |
390 check_die += undef_check_template.format(lane = str(i), n_lane = str(i+1)) | |
391 | |
392 check_die += '\ntest.' + str(len(res.value)) + ':\n' | |
393 check_die += ' ret i32 0' | |
394 | |
395 # Prepare the input values passed to the test function. | |
396 inputs_values = [', '.join([scalar_ty.dump() + ' ' + str(i) for i in inp]) for inp in inputs_values] | |
397 inputs = ', '.join([ty.dump() + ' <' + inp + '>' for inp in inputs_values]) | |
398 | |
399 print(main_template.format(ty = ty.dump(), inputs = inputs, check_die = check_die)) | |
400 | |
401 | |
402 if __name__ == '__main__': | |
403 main() | |
404 | |
405 |