[llvm-exegesis] Fix missing std::move.
[llvm-complete.git] / utils / shuffle_select_fuzz_tester.py
blob88d8d75f7b9894f6669098a8adfcc0c1c775568f
1 #!/usr/bin/env python
3 """A shuffle-select vector fuzz tester.
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.
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 """
17 import random
18 import uuid
19 import argparse
21 # Possibility of one undef index in generated mask for shufflevector instruction
22 SHUF_UNDEF_POS = 0.15
24 # Possibility of one undef index in generated mask for select instruction
25 SEL_UNDEF_POS = 0.15
27 # Possibility of adding a select instruction to the result of a shufflevector
28 ADD_SEL_POS = 0.4
30 # If we are adding a select instruction, this is the possibility of a
31 # merge-select instruction (1 - MERGE_SEL_POS = possibility of zero-merge-select
32 # instruction.
33 MERGE_SEL_POS = 0.5
36 test_template = r'''
37 define internal fastcc {ty} @test({inputs}) noinline nounwind {{
38 entry:
39 {instructions}
40 ret {ty} {last_name}
42 '''
44 error_template = r'''@error.{lane} = private unnamed_addr global [64 x i8] c"FAIL: lane {lane}, expected {exp}, found %d\0A{padding}"'''
46 main_template = r'''
47 define i32 @main() {{
48 entry:
49 ; Create a scratch space to print error messages.
50 %str = alloca [64 x i8]
51 %str.ptr = getelementptr inbounds [64 x i8], [64 x i8]* %str, i32 0, i32 0
53 ; Build the input vector and call the test function.
54 %v = call fastcc {ty} @test({inputs})
55 br label %test.0
57 {check_die}
60 declare i32 @strlen(i8*)
61 declare i32 @write(i32, i8*, i32)
62 declare i32 @sprintf(i8*, i8*, ...)
63 declare void @llvm.trap() noreturn nounwind
64 '''
66 check_template = r'''
67 test.{lane}:
68 %v.{lane} = extractelement {ty} %v, i32 {lane}
69 %cmp.{lane} = {i_f}cmp {ordered}ne {scalar_ty} %v.{lane}, {exp}
70 br i1 %cmp.{lane}, label %die.{lane}, label %test.{n_lane}
71 '''
73 undef_check_template = r'''
74 test.{lane}:
75 ; Skip this lane, its value is undef.
76 br label %test.{n_lane}
77 '''
79 die_template = r'''
80 die.{lane}:
81 ; Capture the actual value and print an error message.
82 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})
83 %length.{lane} = call i32 @strlen(i8* %str.ptr)
84 call i32 @write(i32 2, i8* %str.ptr, i32 %length.{lane})
85 call void @llvm.trap()
86 unreachable
87 '''
89 class Type:
90 def __init__(self, is_float, elt_width, elt_num):
91 self.is_float = is_float # Boolean
92 self.elt_width = elt_width # Integer
93 self.elt_num = elt_num # Integer
95 def dump(self):
96 if self.is_float:
97 str_elt = 'float' if self.elt_width == 32 else 'double'
98 else:
99 str_elt = 'i' + str(self.elt_width)
101 if self.elt_num == 1:
102 return str_elt
103 else:
104 return '<' + str(self.elt_num) + ' x ' + str_elt + '>'
106 def get_scalar_type(self):
107 return Type(self.is_float, self.elt_width, 1)
111 # Class to represent any value (variable) that can be used.
112 class Value:
113 def __init__(self, name, ty, value = None):
114 self.ty = ty # Type
115 self.name = name # String
116 self.value = value # list of integers or floating points
119 # Class to represent an IR instruction (shuffle/select).
120 class Instruction(Value):
121 def __init__(self, name, ty, op0, op1, mask):
122 Value.__init__(self, name, ty)
123 self.op0 = op0 # Value
124 self.op1 = op1 # Value
125 self.mask = mask # list of integers
127 def dump(self): pass
129 def calc_value(self): pass
132 # Class to represent an IR shuffle instruction
133 class ShufInstr(Instruction):
135 shuf_template = ' {name} = shufflevector {ty} {op0}, {ty} {op1}, <{num} x i32> {mask}\n'
137 def __init__(self, name, ty, op0, op1, mask):
138 Instruction.__init__(self, '%shuf' + name, ty, op0, op1, mask)
140 def dump(self):
141 str_mask = [('i32 ' + str(idx)) if idx != -1 else 'i32 undef' for idx in self.mask]
142 str_mask = '<' + (', ').join(str_mask) + '>'
143 return self.shuf_template.format(name = self.name, ty = self.ty.dump(), op0 = self.op0.name,
144 op1 = self.op1.name, num = self.ty.elt_num, mask = str_mask)
146 def calc_value(self):
147 if self.value != None:
148 print 'Trying to calculate the value of a shuffle instruction twice'
149 exit(1)
151 result = []
152 for i in range(len(self.mask)):
153 index = self.mask[i]
155 if index < self.ty.elt_num and index >= 0:
156 result.append(self.op0.value[index])
157 elif index >= self.ty.elt_num:
158 index = index % self.ty.elt_num
159 result.append(self.op1.value[index])
160 else: # -1 => undef
161 result.append(-1)
163 self.value = result
166 # Class to represent an IR select instruction
167 class SelectInstr(Instruction):
169 sel_template = ' {name} = select <{num} x i1> {mask}, {ty} {op0}, {ty} {op1}\n'
171 def __init__(self, name, ty, op0, op1, mask):
172 Instruction.__init__(self, '%sel' + name, ty, op0, op1, mask)
174 def dump(self):
175 str_mask = [('i1 ' + str(idx)) if idx != -1 else 'i1 undef' for idx in self.mask]
176 str_mask = '<' + (', ').join(str_mask) + '>'
177 return self.sel_template.format(name = self.name, ty = self.ty.dump(), op0 = self.op0.name,
178 op1 = self.op1.name, num = self.ty.elt_num, mask = str_mask)
180 def calc_value(self):
181 if self.value != None:
182 print 'Trying to calculate the value of a select instruction twice'
183 exit(1)
185 result = []
186 for i in range(len(self.mask)):
187 index = self.mask[i]
189 if index == 1:
190 result.append(self.op0.value[i])
191 elif index == 0:
192 result.append(self.op1.value[i])
193 else: # -1 => undef
194 result.append(-1)
196 self.value = result
199 # Returns a list of Values initialized with actual numbers according to the
200 # provided type
201 def gen_inputs(ty, num):
202 inputs = []
203 for i in range(num):
204 inp = []
205 for j in range(ty.elt_num):
206 if ty.is_float:
207 inp.append(float(i*ty.elt_num + j))
208 else:
209 inp.append((i*ty.elt_num + j) % (1 << ty.elt_width))
210 inputs.append(Value('%inp' + str(i), ty, inp))
212 return inputs
215 # Returns a random vector type to be tested
216 # In case one of the dimensions (scalar type/number of elements) is provided,
217 # fill the blank dimension and return appropriate Type object.
218 def get_random_type(ty, num_elts):
219 if ty != None:
220 if ty == 'i8':
221 is_float = False
222 width = 8
223 elif ty == 'i16':
224 is_float = False
225 width = 16
226 elif ty == 'i32':
227 is_float = False
228 width = 32
229 elif ty == 'i64':
230 is_float = False
231 width = 64
232 elif ty == 'f32':
233 is_float = True
234 width = 32
235 elif ty == 'f64':
236 is_float = True
237 width = 64
239 int_elt_widths = [8, 16, 32, 64]
240 float_elt_widths = [32, 64]
242 if num_elts == None:
243 num_elts = random.choice(range(2, 65))
245 if ty == None:
246 # 1 for integer type, 0 for floating-point
247 if random.randint(0,1):
248 is_float = False
249 width = random.choice(int_elt_widths)
250 else:
251 is_float = True
252 width = random.choice(float_elt_widths)
254 return Type(is_float, width, num_elts)
257 # Generate mask for shufflevector IR instruction, with SHUF_UNDEF_POS possibility
258 # of one undef index.
259 def gen_shuf_mask(ty):
260 mask = []
261 for i in range(ty.elt_num):
262 if SHUF_UNDEF_POS/ty.elt_num > random.random():
263 mask.append(-1)
264 else:
265 mask.append(random.randint(0, ty.elt_num*2 - 1))
267 return mask
270 # Generate mask for select IR instruction, with SEL_UNDEF_POS possibility
271 # of one undef index.
272 def gen_sel_mask(ty):
273 mask = []
274 for i in range(ty.elt_num):
275 if SEL_UNDEF_POS/ty.elt_num > random.random():
276 mask.append(-1)
277 else:
278 mask.append(random.randint(0, 1))
280 return mask
282 # Generate shuffle instructions with optional select instruction after.
283 def gen_insts(inputs, ty):
284 int_zero_init = Value('zeroinitializer', ty, [0]*ty.elt_num)
285 float_zero_init = Value('zeroinitializer', ty, [0.0]*ty.elt_num)
287 insts = []
288 name_idx = 0
289 while len(inputs) > 1:
290 # Choose 2 available Values - remove them from inputs list.
291 [idx0, idx1] = sorted(random.sample(range(len(inputs)), 2))
292 op0 = inputs[idx0]
293 op1 = inputs[idx1]
295 # Create the shuffle instruction.
296 shuf_mask = gen_shuf_mask(ty)
297 shuf_inst = ShufInstr(str(name_idx), ty, op0, op1, shuf_mask)
298 shuf_inst.calc_value()
300 # Add the new shuffle instruction to the list of instructions.
301 insts.append(shuf_inst)
303 # Optionally, add select instruction with the result of the previous shuffle.
304 if random.random() < ADD_SEL_POS:
305 # Either blending with a random Value or with an all-zero vector.
306 if random.random() < MERGE_SEL_POS:
307 op2 = random.choice(inputs)
308 else:
309 op2 = float_zero_init if ty.is_float else int_zero_init
311 select_mask = gen_sel_mask(ty)
312 select_inst = SelectInstr(str(name_idx), ty, shuf_inst, op2, select_mask)
313 select_inst.calc_value()
315 # Add the select instructions to the list of instructions and to the available Values.
316 insts.append(select_inst)
317 inputs.append(select_inst)
318 else:
319 # If the shuffle instruction is not followed by select, add it to the available Values.
320 inputs.append(shuf_inst)
322 del inputs[idx1]
323 del inputs[idx0]
324 name_idx += 1
326 return insts
329 def main():
330 parser = argparse.ArgumentParser(description=__doc__)
331 parser.add_argument('--seed', default=str(uuid.uuid4()),
332 help='A string used to seed the RNG')
333 parser.add_argument('--max-num-inputs', type=int, default=20,
334 help='Specify the maximum number of vector inputs for the test. (default: 20)')
335 parser.add_argument('--min-num-inputs', type=int, default=10,
336 help='Specify the minimum number of vector inputs for the test. (default: 10)')
337 parser.add_argument('--type', default=None,
338 help='''
339 Choose specific type to be tested.
340 i8, i16, i32, i64, f32 or f64.
341 (default: random)''')
342 parser.add_argument('--num-elts', default=None, type=int,
343 help='Choose specific number of vector elements to be tested. (default: random)')
344 args = parser.parse_args()
346 print '; The seed used for this test is ' + args.seed
348 assert args.min_num_inputs < args.max_num_inputs , "Minimum value greater than maximum."
349 assert args.type in [None, 'i8', 'i16', 'i32', 'i64', 'f32', 'f64'], "Illegal type."
350 assert args.num_elts == None or args.num_elts > 0, "num_elts must be a positive integer."
352 random.seed(args.seed)
353 ty = get_random_type(args.type, args.num_elts)
354 inputs = gen_inputs(ty, random.randint(args.min_num_inputs, args.max_num_inputs))
355 inputs_str = (', ').join([inp.ty.dump() + ' ' + inp.name for inp in inputs])
356 inputs_values = [inp.value for inp in inputs]
358 insts = gen_insts(inputs, ty)
360 assert len(inputs) == 1, "Only one value should be left after generating phase"
361 res = inputs[0]
363 # print the actual test function by dumping the generated instructions.
364 insts_str = ''.join([inst.dump() for inst in insts])
365 print test_template.format(ty = ty.dump(), inputs = inputs_str,
366 instructions = insts_str, last_name = res.name)
368 # Print the error message templates as global strings
369 for i in range(len(res.value)):
370 pad = ''.join(['\\00']*(31 - len(str(i)) - len(str(res.value[i]))))
371 print error_template.format(lane = str(i), exp = str(res.value[i]),
372 padding = pad)
374 # Prepare the runtime checks and failure handlers.
375 scalar_ty = ty.get_scalar_type()
376 check_die = ''
377 i_f = 'f' if ty.is_float else 'i'
378 ordered = 'o' if ty.is_float else ''
379 for i in range(len(res.value)):
380 if res.value[i] != -1:
381 # Emit runtime check for each non-undef expected value.
382 check_die += check_template.format(lane = str(i), n_lane = str(i+1),
383 ty = ty.dump(), i_f = i_f, scalar_ty = scalar_ty.dump(),
384 exp = str(res.value[i]), ordered = ordered)
385 # Emit failure handler for each runtime check with proper error message
386 check_die += die_template.format(lane = str(i), scalar_ty = scalar_ty.dump())
387 else:
388 # Ignore lanes with undef result
389 check_die += undef_check_template.format(lane = str(i), n_lane = str(i+1))
391 check_die += '\ntest.' + str(len(res.value)) + ':\n'
392 check_die += ' ret i32 0'
394 # Prepare the input values passed to the test function.
395 inputs_values = [', '.join([scalar_ty.dump() + ' ' + str(i) for i in inp]) for inp in inputs_values]
396 inputs = ', '.join([ty.dump() + ' <' + inp + '>' for inp in inputs_values])
398 print main_template.format(ty = ty.dump(), inputs = inputs, check_die = check_die)
401 if __name__ == '__main__':
402 main()