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).
16 from __future__
import print_function
22 # Possibility of one undef index in generated mask for shufflevector instruction
25 # Possibility of one undef index in generated mask for select instruction
28 # Possibility of adding a select instruction to the result of a shufflevector
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
38 define internal fastcc {ty} @test({inputs}) noinline nounwind {{
45 error_template
= r
'''@error.{lane} = private unnamed_addr global [64 x i8] c"FAIL: lane {lane}, expected {exp}, found %d\0A{padding}"'''
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
54 ; Build the input vector and call the test function.
55 %v = call fastcc {ty} @test({inputs})
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
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}
74 undef_check_template
= r
'''
76 ; Skip this lane, its value is undef.
77 br label %test.{n_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()
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
98 str_elt
= 'float' if self
.elt_width
== 32 else 'double'
100 str_elt
= 'i' + str(self
.elt_width
)
102 if self
.elt_num
== 1:
105 return '<' + str(self
.elt_num
) + ' x ' + str_elt
+ '>'
107 def get_scalar_type(self
):
108 return Type(self
.is_float
, self
.elt_width
, 1)
112 # Class to represent any value (variable) that can be used.
114 def __init__(self
, name
, ty
, value
= None):
116 self
.name
= name
# String
117 self
.value
= value
# list of integers or floating points
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
130 def calc_value(self
): pass
133 # Class to represent an IR shuffle instruction
134 class ShufInstr(Instruction
):
136 shuf_template
= ' {name} = shufflevector {ty} {op0}, {ty} {op1}, <{num} x i32> {mask}\n'
138 def __init__(self
, name
, ty
, op0
, op1
, mask
):
139 Instruction
.__init
__(self
, '%shuf' + name
, ty
, op0
, op1
, mask
)
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
)
147 def calc_value(self
):
148 if self
.value
!= None:
149 print('Trying to calculate the value of a shuffle instruction twice')
153 for i
in range(len(self
.mask
)):
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
])
167 # Class to represent an IR select instruction
168 class SelectInstr(Instruction
):
170 sel_template
= ' {name} = select <{num} x i1> {mask}, {ty} {op0}, {ty} {op1}\n'
172 def __init__(self
, name
, ty
, op0
, op1
, mask
):
173 Instruction
.__init
__(self
, '%sel' + name
, ty
, op0
, op1
, mask
)
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
)
181 def calc_value(self
):
182 if self
.value
!= None:
183 print('Trying to calculate the value of a select instruction twice')
187 for i
in range(len(self
.mask
)):
191 result
.append(self
.op0
.value
[i
])
193 result
.append(self
.op1
.value
[i
])
200 # Returns a list of Values initialized with actual numbers according to the
202 def gen_inputs(ty
, num
):
206 for j
in range(ty
.elt_num
):
208 inp
.append(float(i
*ty
.elt_num
+ j
))
210 inp
.append((i
*ty
.elt_num
+ j
) % (1 << ty
.elt_width
))
211 inputs
.append(Value('%inp' + str(i
), ty
, inp
))
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
):
240 int_elt_widths
= [8, 16, 32, 64]
241 float_elt_widths
= [32, 64]
244 num_elts
= random
.choice(range(2, 65))
247 # 1 for integer type, 0 for floating-point
248 if random
.randint(0,1):
250 width
= random
.choice(int_elt_widths
)
253 width
= random
.choice(float_elt_widths
)
255 return Type(is_float
, width
, num_elts
)
258 # Generate mask for shufflevector IR instruction, with SHUF_UNDEF_POS possibility
259 # of one undef index.
260 def gen_shuf_mask(ty
):
262 for i
in range(ty
.elt_num
):
263 if SHUF_UNDEF_POS
/ty
.elt_num
> random
.random():
266 mask
.append(random
.randint(0, ty
.elt_num
*2 - 1))
271 # Generate mask for select IR instruction, with SEL_UNDEF_POS possibility
272 # of one undef index.
273 def gen_sel_mask(ty
):
275 for i
in range(ty
.elt_num
):
276 if SEL_UNDEF_POS
/ty
.elt_num
> random
.random():
279 mask
.append(random
.randint(0, 1))
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
)
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))
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()
301 # Add the new shuffle instruction to the list of instructions.
302 insts
.append(shuf_inst
)
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
)
310 op2
= float_zero_init
if ty
.is_float
else int_zero_init
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()
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
)
320 # If the shuffle instruction is not followed by select, add it to the available Values.
321 inputs
.append(shuf_inst
)
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,
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()
347 print('; The seed used for this test is ' + args
.seed
)
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."
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
]
359 insts
= gen_insts(inputs
, ty
)
361 assert len(inputs
) == 1, "Only one value should be left after generating phase"
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
))
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
]),
375 # Prepare the runtime checks and failure handlers.
376 scalar_ty
= ty
.get_scalar_type()
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())
389 # Ignore lanes with undef result
390 check_die
+= undef_check_template
.format(lane
= str(i
), n_lane
= str(i
+1))
392 check_die
+= '\ntest.' + str(len(res
.value
)) + ':\n'
393 check_die
+= ' ret i32 0'
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
])
399 print(main_template
.format(ty
= ty
.dump(), inputs
= inputs
, check_die
= check_die
))
402 if __name__
== '__main__':