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()
92 def __init__(self
, is_float
, elt_width
, elt_num
):
93 self
.is_float
= is_float
# Boolean
94 self
.elt_width
= elt_width
# Integer
95 self
.elt_num
= elt_num
# Integer
99 str_elt
= "float" if self
.elt_width
== 32 else "double"
101 str_elt
= "i" + str(self
.elt_width
)
103 if self
.elt_num
== 1:
106 return "<" + str(self
.elt_num
) + " x " + str_elt
+ ">"
108 def get_scalar_type(self
):
109 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
131 def calc_value(self
):
135 # Class to represent an IR shuffle instruction
136 class ShufInstr(Instruction
):
139 " {name} = shufflevector {ty} {op0}, {ty} {op1}, <{num} x i32> {mask}\n"
142 def __init__(self
, name
, ty
, op0
, op1
, mask
):
143 Instruction
.__init
__(self
, "%shuf" + name
, ty
, op0
, op1
, mask
)
147 ("i32 " + str(idx
)) if idx
!= -1 else "i32 undef" for idx
in self
.mask
149 str_mask
= "<" + (", ").join(str_mask
) + ">"
150 return self
.shuf_template
.format(
159 def calc_value(self
):
160 if self
.value
is not None:
161 print("Trying to calculate the value of a shuffle instruction twice")
165 for i
in range(len(self
.mask
)):
168 if index
< self
.ty
.elt_num
and index
>= 0:
169 result
.append(self
.op0
.value
[index
])
170 elif index
>= self
.ty
.elt_num
:
171 index
= index
% self
.ty
.elt_num
172 result
.append(self
.op1
.value
[index
])
179 # Class to represent an IR select instruction
180 class SelectInstr(Instruction
):
182 sel_template
= " {name} = select <{num} x i1> {mask}, {ty} {op0}, {ty} {op1}\n"
184 def __init__(self
, name
, ty
, op0
, op1
, mask
):
185 Instruction
.__init
__(self
, "%sel" + name
, ty
, op0
, op1
, mask
)
189 ("i1 " + str(idx
)) if idx
!= -1 else "i1 undef" for idx
in self
.mask
191 str_mask
= "<" + (", ").join(str_mask
) + ">"
192 return self
.sel_template
.format(
201 def calc_value(self
):
202 if self
.value
is not None:
203 print("Trying to calculate the value of a select instruction twice")
207 for i
in range(len(self
.mask
)):
211 result
.append(self
.op0
.value
[i
])
213 result
.append(self
.op1
.value
[i
])
220 # Returns a list of Values initialized with actual numbers according to the
222 def gen_inputs(ty
, num
):
226 for j
in range(ty
.elt_num
):
228 inp
.append(float(i
* ty
.elt_num
+ j
))
230 inp
.append((i
* ty
.elt_num
+ j
) % (1 << ty
.elt_width
))
231 inputs
.append(Value("%inp" + str(i
), ty
, inp
))
236 # Returns a random vector type to be tested
237 # In case one of the dimensions (scalar type/number of elements) is provided,
238 # fill the blank dimension and return appropriate Type object.
239 def get_random_type(ty
, num_elts
):
260 int_elt_widths
= [8, 16, 32, 64]
261 float_elt_widths
= [32, 64]
264 num_elts
= random
.choice(range(2, 65))
267 # 1 for integer type, 0 for floating-point
268 if random
.randint(0, 1):
270 width
= random
.choice(int_elt_widths
)
273 width
= random
.choice(float_elt_widths
)
275 return Type(is_float
, width
, num_elts
)
278 # Generate mask for shufflevector IR instruction, with SHUF_UNDEF_POS possibility
279 # of one undef index.
280 def gen_shuf_mask(ty
):
282 for i
in range(ty
.elt_num
):
283 if SHUF_UNDEF_POS
/ ty
.elt_num
> random
.random():
286 mask
.append(random
.randint(0, ty
.elt_num
* 2 - 1))
291 # Generate mask for select IR instruction, with SEL_UNDEF_POS possibility
292 # of one undef index.
293 def gen_sel_mask(ty
):
295 for i
in range(ty
.elt_num
):
296 if SEL_UNDEF_POS
/ ty
.elt_num
> random
.random():
299 mask
.append(random
.randint(0, 1))
304 # Generate shuffle instructions with optional select instruction after.
305 def gen_insts(inputs
, ty
):
306 int_zero_init
= Value("zeroinitializer", ty
, [0] * ty
.elt_num
)
307 float_zero_init
= Value("zeroinitializer", ty
, [0.0] * ty
.elt_num
)
311 while len(inputs
) > 1:
312 # Choose 2 available Values - remove them from inputs list.
313 [idx0
, idx1
] = sorted(random
.sample(range(len(inputs
)), 2))
317 # Create the shuffle instruction.
318 shuf_mask
= gen_shuf_mask(ty
)
319 shuf_inst
= ShufInstr(str(name_idx
), ty
, op0
, op1
, shuf_mask
)
320 shuf_inst
.calc_value()
322 # Add the new shuffle instruction to the list of instructions.
323 insts
.append(shuf_inst
)
325 # Optionally, add select instruction with the result of the previous shuffle.
326 if random
.random() < ADD_SEL_POS
:
327 # Either blending with a random Value or with an all-zero vector.
328 if random
.random() < MERGE_SEL_POS
:
329 op2
= random
.choice(inputs
)
331 op2
= float_zero_init
if ty
.is_float
else int_zero_init
333 select_mask
= gen_sel_mask(ty
)
334 select_inst
= SelectInstr(str(name_idx
), ty
, shuf_inst
, op2
, select_mask
)
335 select_inst
.calc_value()
337 # Add the select instructions to the list of instructions and to the available Values.
338 insts
.append(select_inst
)
339 inputs
.append(select_inst
)
341 # If the shuffle instruction is not followed by select, add it to the available Values.
342 inputs
.append(shuf_inst
)
352 parser
= argparse
.ArgumentParser(description
=__doc__
)
354 "--seed", default
=str(uuid
.uuid4()), help="A string used to seed the RNG"
360 help="Specify the maximum number of vector inputs for the test. (default: 20)",
366 help="Specify the minimum number of vector inputs for the test. (default: 10)",
372 Choose specific type to be tested.
373 i8, i16, i32, i64, f32 or f64.
374 (default: random)""",
380 help="Choose specific number of vector elements to be tested. (default: random)",
382 args
= parser
.parse_args()
384 print("; The seed used for this test is " + args
.seed
)
387 args
.min_num_inputs
< args
.max_num_inputs
388 ), "Minimum value greater than maximum."
389 assert args
.type in [None, "i8", "i16", "i32", "i64", "f32", "f64"], "Illegal type."
391 args
.num_elts
is None or args
.num_elts
> 0
392 ), "num_elts must be a positive integer."
394 random
.seed(args
.seed
)
395 ty
= get_random_type(args
.type, args
.num_elts
)
396 inputs
= gen_inputs(ty
, random
.randint(args
.min_num_inputs
, args
.max_num_inputs
))
397 inputs_str
= (", ").join([inp
.ty
.dump() + " " + inp
.name
for inp
in inputs
])
398 inputs_values
= [inp
.value
for inp
in inputs
]
400 insts
= gen_insts(inputs
, ty
)
402 assert len(inputs
) == 1, "Only one value should be left after generating phase"
405 # print the actual test function by dumping the generated instructions.
406 insts_str
= "".join([inst
.dump() for inst
in insts
])
408 test_template
.format(
409 ty
=ty
.dump(), inputs
=inputs_str
, instructions
=insts_str
, last_name
=res
.name
413 # Print the error message templates as global strings
414 for i
in range(len(res
.value
)):
415 pad
= "".join(["\\00"] * (31 - len(str(i
)) - len(str(res
.value
[i
]))))
416 print(error_template
.format(lane
=str(i
), exp
=str(res
.value
[i
]), padding
=pad
))
418 # Prepare the runtime checks and failure handlers.
419 scalar_ty
= ty
.get_scalar_type()
421 i_f
= "f" if ty
.is_float
else "i"
422 ordered
= "o" if ty
.is_float
else ""
423 for i
in range(len(res
.value
)):
424 if res
.value
[i
] != -1:
425 # Emit runtime check for each non-undef expected value.
426 check_die
+= check_template
.format(
431 scalar_ty
=scalar_ty
.dump(),
432 exp
=str(res
.value
[i
]),
435 # Emit failure handler for each runtime check with proper error message
436 check_die
+= die_template
.format(lane
=str(i
), scalar_ty
=scalar_ty
.dump())
438 # Ignore lanes with undef result
439 check_die
+= undef_check_template
.format(lane
=str(i
), n_lane
=str(i
+ 1))
441 check_die
+= "\ntest." + str(len(res
.value
)) + ":\n"
442 check_die
+= " ret i32 0"
444 # Prepare the input values passed to the test function.
446 ", ".join([scalar_ty
.dump() + " " + str(i
) for i
in inp
])
447 for inp
in inputs_values
449 inputs
= ", ".join([ty
.dump() + " <" + inp
+ ">" for inp
in inputs_values
])
451 print(main_template
.format(ty
=ty
.dump(), inputs
=inputs
, check_die
=check_die
))
454 if __name__
== "__main__":