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).
21 # Possibility of one undef index in generated mask for shufflevector instruction
24 # Possibility of one undef index in generated mask for select instruction
27 # Possibility of adding a select instruction to the result of a shufflevector
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
37 define internal fastcc {ty} @test({inputs}) noinline nounwind {{
44 error_template
= r
'''@error.{lane} = private unnamed_addr global [64 x i8] c"FAIL: lane {lane}, expected {exp}, found %d\0A{padding}"'''
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})
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
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}
73 undef_check_template
= r
'''
75 ; Skip this lane, its value is undef.
76 br label %test.{n_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()
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
97 str_elt
= 'float' if self
.elt_width
== 32 else 'double'
99 str_elt
= 'i' + str(self
.elt_width
)
101 if self
.elt_num
== 1:
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.
113 def __init__(self
, name
, ty
, value
= None):
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
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
)
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'
152 for i
in range(len(self
.mask
)):
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
])
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
)
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'
186 for i
in range(len(self
.mask
)):
190 result
.append(self
.op0
.value
[i
])
192 result
.append(self
.op1
.value
[i
])
199 # Returns a list of Values initialized with actual numbers according to the
201 def gen_inputs(ty
, num
):
205 for j
in range(ty
.elt_num
):
207 inp
.append(float(i
*ty
.elt_num
+ j
))
209 inp
.append((i
*ty
.elt_num
+ j
) % (1 << ty
.elt_width
))
210 inputs
.append(Value('%inp' + str(i
), ty
, inp
))
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
):
239 int_elt_widths
= [8, 16, 32, 64]
240 float_elt_widths
= [32, 64]
243 num_elts
= random
.choice(range(2, 65))
246 # 1 for integer type, 0 for floating-point
247 if random
.randint(0,1):
249 width
= random
.choice(int_elt_widths
)
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
):
261 for i
in range(ty
.elt_num
):
262 if SHUF_UNDEF_POS
/ty
.elt_num
> random
.random():
265 mask
.append(random
.randint(0, ty
.elt_num
*2 - 1))
270 # Generate mask for select IR instruction, with SEL_UNDEF_POS possibility
271 # of one undef index.
272 def gen_sel_mask(ty
):
274 for i
in range(ty
.elt_num
):
275 if SEL_UNDEF_POS
/ty
.elt_num
> random
.random():
278 mask
.append(random
.randint(0, 1))
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
)
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))
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
)
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
)
319 # If the shuffle instruction is not followed by select, add it to the available Values.
320 inputs
.append(shuf_inst
)
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,
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"
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
]),
374 # Prepare the runtime checks and failure handlers.
375 scalar_ty
= ty
.get_scalar_type()
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())
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__':