mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-24 23:06:05 +00:00

from PEP8 (https://peps.python.org/pep-0008/#programming-recommendations): > Comparisons to singletons like None should always be done with is or is not, never the equality operators. Co-authored-by: Eisuke Kawashima <e-kwsm@users.noreply.github.com>
456 lines
14 KiB
Python
Executable File
456 lines
14 KiB
Python
Executable File
#!/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()
|