|  | #!/usr/bin/env python | 
|  |  | 
|  | """A shuffle-select vector fuzz tester. | 
|  |  | 
|  | This is a python program to fuzz test the LLVM shufflevector and select | 
|  | instructions. It generates a function with a random sequnece of shufflevectors | 
|  | while optionally attaching it with a select instruction (regular or zero merge), | 
|  | maintaining the element mapping accumulated across the function. It then | 
|  | generates a main function which calls it with a different value in each element | 
|  | and checks that the result matches the expected mapping. | 
|  |  | 
|  | Take the output IR printed to stdout, compile it to an executable using whatever | 
|  | set of transforms you want to test, and run the program. If it crashes, it found | 
|  | a bug (an error message with the expected and actual result is printed). | 
|  | """ | 
|  | from __future__ import print_function | 
|  |  | 
|  | import random | 
|  | import uuid | 
|  | import argparse | 
|  |  | 
|  | # Possibility of one undef index in generated mask for shufflevector instruction | 
|  | SHUF_UNDEF_POS = 0.15 | 
|  |  | 
|  | # Possibility of one undef index in generated mask for select instruction | 
|  | SEL_UNDEF_POS = 0.15 | 
|  |  | 
|  | # Possibility of adding a select instruction to the result of a shufflevector | 
|  | ADD_SEL_POS = 0.4 | 
|  |  | 
|  | # If we are adding a select instruction, this is the possibility of a | 
|  | # merge-select instruction (1 - MERGE_SEL_POS = possibility of zero-merge-select | 
|  | # instruction. | 
|  | MERGE_SEL_POS = 0.5 | 
|  |  | 
|  |  | 
|  | test_template = r""" | 
|  | define internal fastcc {ty} @test({inputs}) noinline nounwind {{ | 
|  | entry: | 
|  | {instructions} | 
|  | ret {ty} {last_name} | 
|  | }} | 
|  | """ | 
|  |  | 
|  | error_template = r'''@error.{lane} = private unnamed_addr global [64 x i8] c"FAIL: lane {lane}, expected {exp}, found %d\0A{padding}"''' | 
|  |  | 
|  | main_template = r""" | 
|  | define i32 @main() {{ | 
|  | entry: | 
|  | ; Create a scratch space to print error messages. | 
|  | %str = alloca [64 x i8] | 
|  | %str.ptr = getelementptr inbounds [64 x i8], [64 x i8]* %str, i32 0, i32 0 | 
|  |  | 
|  | ; Build the input vector and call the test function. | 
|  | %v = call fastcc {ty} @test({inputs}) | 
|  | br label %test.0 | 
|  |  | 
|  | {check_die} | 
|  | }} | 
|  |  | 
|  | declare i32 @strlen(i8*) | 
|  | declare i32 @write(i32, i8*, i32) | 
|  | declare i32 @sprintf(i8*, i8*, ...) | 
|  | declare void @llvm.trap() noreturn nounwind | 
|  | """ | 
|  |  | 
|  | check_template = r""" | 
|  | test.{lane}: | 
|  | %v.{lane} = extractelement {ty} %v, i32 {lane} | 
|  | %cmp.{lane} = {i_f}cmp {ordered}ne {scalar_ty} %v.{lane}, {exp} | 
|  | br i1 %cmp.{lane}, label %die.{lane}, label %test.{n_lane} | 
|  | """ | 
|  |  | 
|  | undef_check_template = r""" | 
|  | test.{lane}: | 
|  | ; Skip this lane, its value is undef. | 
|  | br label %test.{n_lane} | 
|  | """ | 
|  |  | 
|  | die_template = r""" | 
|  | die.{lane}: | 
|  | ; Capture the actual value and print an error message. | 
|  | 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}) | 
|  | %length.{lane} = call i32 @strlen(i8* %str.ptr) | 
|  | call i32 @write(i32 2, i8* %str.ptr, i32 %length.{lane}) | 
|  | call void @llvm.trap() | 
|  | unreachable | 
|  | """ | 
|  |  | 
|  |  | 
|  | class Type: | 
|  | def __init__(self, is_float, elt_width, elt_num): | 
|  | self.is_float = is_float  # Boolean | 
|  | self.elt_width = elt_width  # Integer | 
|  | self.elt_num = elt_num  # Integer | 
|  |  | 
|  | def dump(self): | 
|  | if self.is_float: | 
|  | str_elt = "float" if self.elt_width == 32 else "double" | 
|  | else: | 
|  | str_elt = "i" + str(self.elt_width) | 
|  |  | 
|  | if self.elt_num == 1: | 
|  | return str_elt | 
|  | else: | 
|  | return "<" + str(self.elt_num) + " x " + str_elt + ">" | 
|  |  | 
|  | def get_scalar_type(self): | 
|  | return Type(self.is_float, self.elt_width, 1) | 
|  |  | 
|  |  | 
|  | # Class to represent any value (variable) that can be used. | 
|  | class Value: | 
|  | def __init__(self, name, ty, value=None): | 
|  | self.ty = ty  # Type | 
|  | self.name = name  # String | 
|  | self.value = value  # list of integers or floating points | 
|  |  | 
|  |  | 
|  | # Class to represent an IR instruction (shuffle/select). | 
|  | class Instruction(Value): | 
|  | def __init__(self, name, ty, op0, op1, mask): | 
|  | Value.__init__(self, name, ty) | 
|  | self.op0 = op0  # Value | 
|  | self.op1 = op1  # Value | 
|  | self.mask = mask  # list of integers | 
|  |  | 
|  | def dump(self): | 
|  | pass | 
|  |  | 
|  | def calc_value(self): | 
|  | pass | 
|  |  | 
|  |  | 
|  | # Class to represent an IR shuffle instruction | 
|  | class ShufInstr(Instruction): | 
|  |  | 
|  | shuf_template = ( | 
|  | "  {name} = shufflevector {ty} {op0}, {ty} {op1}, <{num} x i32> {mask}\n" | 
|  | ) | 
|  |  | 
|  | def __init__(self, name, ty, op0, op1, mask): | 
|  | Instruction.__init__(self, "%shuf" + name, ty, op0, op1, mask) | 
|  |  | 
|  | def dump(self): | 
|  | str_mask = [ | 
|  | ("i32 " + str(idx)) if idx != -1 else "i32 undef" for idx in self.mask | 
|  | ] | 
|  | str_mask = "<" + (", ").join(str_mask) + ">" | 
|  | return self.shuf_template.format( | 
|  | name=self.name, | 
|  | ty=self.ty.dump(), | 
|  | op0=self.op0.name, | 
|  | op1=self.op1.name, | 
|  | num=self.ty.elt_num, | 
|  | mask=str_mask, | 
|  | ) | 
|  |  | 
|  | def calc_value(self): | 
|  | if self.value is not None: | 
|  | print("Trying to calculate the value of a shuffle instruction twice") | 
|  | exit(1) | 
|  |  | 
|  | result = [] | 
|  | for i in range(len(self.mask)): | 
|  | index = self.mask[i] | 
|  |  | 
|  | if index < self.ty.elt_num and index >= 0: | 
|  | result.append(self.op0.value[index]) | 
|  | elif index >= self.ty.elt_num: | 
|  | index = index % self.ty.elt_num | 
|  | result.append(self.op1.value[index]) | 
|  | else:  # -1 => undef | 
|  | result.append(-1) | 
|  |  | 
|  | self.value = result | 
|  |  | 
|  |  | 
|  | # Class to represent an IR select instruction | 
|  | class SelectInstr(Instruction): | 
|  |  | 
|  | sel_template = "  {name} = select <{num} x i1> {mask}, {ty} {op0}, {ty} {op1}\n" | 
|  |  | 
|  | def __init__(self, name, ty, op0, op1, mask): | 
|  | Instruction.__init__(self, "%sel" + name, ty, op0, op1, mask) | 
|  |  | 
|  | def dump(self): | 
|  | str_mask = [ | 
|  | ("i1 " + str(idx)) if idx != -1 else "i1 undef" for idx in self.mask | 
|  | ] | 
|  | str_mask = "<" + (", ").join(str_mask) + ">" | 
|  | return self.sel_template.format( | 
|  | name=self.name, | 
|  | ty=self.ty.dump(), | 
|  | op0=self.op0.name, | 
|  | op1=self.op1.name, | 
|  | num=self.ty.elt_num, | 
|  | mask=str_mask, | 
|  | ) | 
|  |  | 
|  | def calc_value(self): | 
|  | if self.value is not None: | 
|  | print("Trying to calculate the value of a select instruction twice") | 
|  | exit(1) | 
|  |  | 
|  | result = [] | 
|  | for i in range(len(self.mask)): | 
|  | index = self.mask[i] | 
|  |  | 
|  | if index == 1: | 
|  | result.append(self.op0.value[i]) | 
|  | elif index == 0: | 
|  | result.append(self.op1.value[i]) | 
|  | else:  # -1 => undef | 
|  | result.append(-1) | 
|  |  | 
|  | self.value = result | 
|  |  | 
|  |  | 
|  | # Returns a list of Values initialized with actual numbers according to the | 
|  | # provided type | 
|  | def gen_inputs(ty, num): | 
|  | inputs = [] | 
|  | for i in range(num): | 
|  | inp = [] | 
|  | for j in range(ty.elt_num): | 
|  | if ty.is_float: | 
|  | inp.append(float(i * ty.elt_num + j)) | 
|  | else: | 
|  | inp.append((i * ty.elt_num + j) % (1 << ty.elt_width)) | 
|  | inputs.append(Value("%inp" + str(i), ty, inp)) | 
|  |  | 
|  | return inputs | 
|  |  | 
|  |  | 
|  | # Returns a random vector type to be tested | 
|  | # In case one of the dimensions (scalar type/number of elements) is provided, | 
|  | # fill the blank dimension and return appropriate Type object. | 
|  | def get_random_type(ty, num_elts): | 
|  | if ty is not None: | 
|  | if ty == "i8": | 
|  | is_float = False | 
|  | width = 8 | 
|  | elif ty == "i16": | 
|  | is_float = False | 
|  | width = 16 | 
|  | elif ty == "i32": | 
|  | is_float = False | 
|  | width = 32 | 
|  | elif ty == "i64": | 
|  | is_float = False | 
|  | width = 64 | 
|  | elif ty == "f32": | 
|  | is_float = True | 
|  | width = 32 | 
|  | elif ty == "f64": | 
|  | is_float = True | 
|  | width = 64 | 
|  |  | 
|  | int_elt_widths = [8, 16, 32, 64] | 
|  | float_elt_widths = [32, 64] | 
|  |  | 
|  | if num_elts is None: | 
|  | num_elts = random.choice(range(2, 65)) | 
|  |  | 
|  | if ty is None: | 
|  | # 1 for integer type, 0 for floating-point | 
|  | if random.randint(0, 1): | 
|  | is_float = False | 
|  | width = random.choice(int_elt_widths) | 
|  | else: | 
|  | is_float = True | 
|  | width = random.choice(float_elt_widths) | 
|  |  | 
|  | return Type(is_float, width, num_elts) | 
|  |  | 
|  |  | 
|  | # Generate mask for shufflevector IR instruction, with SHUF_UNDEF_POS possibility | 
|  | # of one undef index. | 
|  | def gen_shuf_mask(ty): | 
|  | mask = [] | 
|  | for i in range(ty.elt_num): | 
|  | if SHUF_UNDEF_POS / ty.elt_num > random.random(): | 
|  | mask.append(-1) | 
|  | else: | 
|  | mask.append(random.randint(0, ty.elt_num * 2 - 1)) | 
|  |  | 
|  | return mask | 
|  |  | 
|  |  | 
|  | # Generate mask for select IR instruction, with SEL_UNDEF_POS possibility | 
|  | # of one undef index. | 
|  | def gen_sel_mask(ty): | 
|  | mask = [] | 
|  | for i in range(ty.elt_num): | 
|  | if SEL_UNDEF_POS / ty.elt_num > random.random(): | 
|  | mask.append(-1) | 
|  | else: | 
|  | mask.append(random.randint(0, 1)) | 
|  |  | 
|  | return mask | 
|  |  | 
|  |  | 
|  | # Generate shuffle instructions with optional select instruction after. | 
|  | def gen_insts(inputs, ty): | 
|  | int_zero_init = Value("zeroinitializer", ty, [0] * ty.elt_num) | 
|  | float_zero_init = Value("zeroinitializer", ty, [0.0] * ty.elt_num) | 
|  |  | 
|  | insts = [] | 
|  | name_idx = 0 | 
|  | while len(inputs) > 1: | 
|  | # Choose 2 available Values - remove them from inputs list. | 
|  | [idx0, idx1] = sorted(random.sample(range(len(inputs)), 2)) | 
|  | op0 = inputs[idx0] | 
|  | op1 = inputs[idx1] | 
|  |  | 
|  | # Create the shuffle instruction. | 
|  | shuf_mask = gen_shuf_mask(ty) | 
|  | shuf_inst = ShufInstr(str(name_idx), ty, op0, op1, shuf_mask) | 
|  | shuf_inst.calc_value() | 
|  |  | 
|  | # Add the new shuffle instruction to the list of instructions. | 
|  | insts.append(shuf_inst) | 
|  |  | 
|  | # Optionally, add select instruction with the result of the previous shuffle. | 
|  | if random.random() < ADD_SEL_POS: | 
|  | #  Either blending with a random Value or with an all-zero vector. | 
|  | if random.random() < MERGE_SEL_POS: | 
|  | op2 = random.choice(inputs) | 
|  | else: | 
|  | op2 = float_zero_init if ty.is_float else int_zero_init | 
|  |  | 
|  | select_mask = gen_sel_mask(ty) | 
|  | select_inst = SelectInstr(str(name_idx), ty, shuf_inst, op2, select_mask) | 
|  | select_inst.calc_value() | 
|  |  | 
|  | # Add the select instructions to the list of instructions and to the available Values. | 
|  | insts.append(select_inst) | 
|  | inputs.append(select_inst) | 
|  | else: | 
|  | # If the shuffle instruction is not followed by select, add it to the available Values. | 
|  | inputs.append(shuf_inst) | 
|  |  | 
|  | del inputs[idx1] | 
|  | del inputs[idx0] | 
|  | name_idx += 1 | 
|  |  | 
|  | return insts | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser(description=__doc__) | 
|  | parser.add_argument( | 
|  | "--seed", default=str(uuid.uuid4()), help="A string used to seed the RNG" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--max-num-inputs", | 
|  | type=int, | 
|  | default=20, | 
|  | help="Specify the maximum number of vector inputs for the test. (default: 20)", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--min-num-inputs", | 
|  | type=int, | 
|  | default=10, | 
|  | help="Specify the minimum number of vector inputs for the test. (default: 10)", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--type", | 
|  | default=None, | 
|  | help=""" | 
|  | Choose specific type to be tested. | 
|  | i8, i16, i32, i64, f32 or f64. | 
|  | (default: random)""", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--num-elts", | 
|  | default=None, | 
|  | type=int, | 
|  | help="Choose specific number of vector elements to be tested. (default: random)", | 
|  | ) | 
|  | args = parser.parse_args() | 
|  |  | 
|  | print("; The seed used for this test is " + args.seed) | 
|  |  | 
|  | assert ( | 
|  | args.min_num_inputs < args.max_num_inputs | 
|  | ), "Minimum value greater than maximum." | 
|  | assert args.type in [None, "i8", "i16", "i32", "i64", "f32", "f64"], "Illegal type." | 
|  | assert ( | 
|  | args.num_elts is None or args.num_elts > 0 | 
|  | ), "num_elts must be a positive integer." | 
|  |  | 
|  | random.seed(args.seed) | 
|  | ty = get_random_type(args.type, args.num_elts) | 
|  | inputs = gen_inputs(ty, random.randint(args.min_num_inputs, args.max_num_inputs)) | 
|  | inputs_str = (", ").join([inp.ty.dump() + " " + inp.name for inp in inputs]) | 
|  | inputs_values = [inp.value for inp in inputs] | 
|  |  | 
|  | insts = gen_insts(inputs, ty) | 
|  |  | 
|  | assert len(inputs) == 1, "Only one value should be left after generating phase" | 
|  | res = inputs[0] | 
|  |  | 
|  | # print the actual test function by dumping the generated instructions. | 
|  | insts_str = "".join([inst.dump() for inst in insts]) | 
|  | print( | 
|  | test_template.format( | 
|  | ty=ty.dump(), inputs=inputs_str, instructions=insts_str, last_name=res.name | 
|  | ) | 
|  | ) | 
|  |  | 
|  | # Print the error message templates as global strings | 
|  | for i in range(len(res.value)): | 
|  | pad = "".join(["\\00"] * (31 - len(str(i)) - len(str(res.value[i])))) | 
|  | print(error_template.format(lane=str(i), exp=str(res.value[i]), padding=pad)) | 
|  |  | 
|  | # Prepare the runtime checks and failure handlers. | 
|  | scalar_ty = ty.get_scalar_type() | 
|  | check_die = "" | 
|  | i_f = "f" if ty.is_float else "i" | 
|  | ordered = "o" if ty.is_float else "" | 
|  | for i in range(len(res.value)): | 
|  | if res.value[i] != -1: | 
|  | # Emit runtime check for each non-undef expected value. | 
|  | check_die += check_template.format( | 
|  | lane=str(i), | 
|  | n_lane=str(i + 1), | 
|  | ty=ty.dump(), | 
|  | i_f=i_f, | 
|  | scalar_ty=scalar_ty.dump(), | 
|  | exp=str(res.value[i]), | 
|  | ordered=ordered, | 
|  | ) | 
|  | # Emit failure handler for each runtime check with proper error message | 
|  | check_die += die_template.format(lane=str(i), scalar_ty=scalar_ty.dump()) | 
|  | else: | 
|  | # Ignore lanes with undef result | 
|  | check_die += undef_check_template.format(lane=str(i), n_lane=str(i + 1)) | 
|  |  | 
|  | check_die += "\ntest." + str(len(res.value)) + ":\n" | 
|  | check_die += "  ret i32 0" | 
|  |  | 
|  | # Prepare the input values passed to the test function. | 
|  | inputs_values = [ | 
|  | ", ".join([scalar_ty.dump() + " " + str(i) for i in inp]) | 
|  | for inp in inputs_values | 
|  | ] | 
|  | inputs = ", ".join([ty.dump() + " <" + inp + ">" for inp in inputs_values]) | 
|  |  | 
|  | print(main_template.format(ty=ty.dump(), inputs=inputs, check_die=check_die)) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |