blob: 1f1a0478f2bbc46fbf60c0d631c46ac6fb395d95 [file] [log] [blame]
Chandler Carruth7f3facb2014-08-07 04:13:51 +00001#!/usr/bin/env python
2
3"""A shuffle vector fuzz tester.
4
5This is a python program to fuzz test the LLVM shufflevector instruction. It
6generates a function with a random sequnece of shufflevectors, maintaining the
7element mapping accumulated across the function. It then generates a main
8function which calls it with a different value in each element and checks that
9the result matches the expected mapping.
10
11Take the output IR printed to stdout, compile it to an executable using whatever
12set of transforms you want to test, and run the program. If it crashes, it found
13a bug.
14"""
15
16import argparse
17import itertools
18import random
19import sys
20
21def main():
22 parser = argparse.ArgumentParser(description=__doc__)
23 parser.add_argument('seed',
24 help='A string used to seed the RNG')
25 parser.add_argument('-v', '--verbose', action='store_true',
26 help='Show verbose output')
Chandler Carruth8edd4972014-08-13 09:05:59 +000027 parser.add_argument('--max-shuffle-height', type=int, default=16,
28 help='Specify a fixed height of shuffle tree to test')
29 parser.add_argument('--no-blends', dest='blends', action='store_false',
30 help='Include blends of two input vectors')
Chandler Carruthae42d7d2014-08-07 04:49:54 +000031 parser.add_argument('--fixed-bit-width', type=int, choices=[128, 256],
32 help='Specify a fixed bit width of vector to test')
Chandler Carruth7f3facb2014-08-07 04:13:51 +000033 parser.add_argument('--triple',
34 help='Specify a triple string to include in the IR')
35 args = parser.parse_args()
36
37 random.seed(args.seed)
38
Chandler Carruthae42d7d2014-08-07 04:49:54 +000039 if args.fixed_bit_width is not None:
40 if args.fixed_bit_width == 128:
41 (width, element_type) = random.choice(
42 [(2, 'i64'), (4, 'i32'), (8, 'i16'), (16, 'i8'),
43 (2, 'f64'), (4, 'f32')])
44 elif args.fixed_bit_width == 256:
45 (width, element_type) = random.choice([
46 (4, 'i64'), (8, 'i32'), (16, 'i16'), (32, 'i8'),
47 (4, 'f64'), (8, 'f32')])
48 else:
49 sys.exit(1) # Checked above by argument parsing.
50 else:
51 width = random.choice([2, 4, 8, 16, 32, 64])
52 element_type = random.choice(['i8', 'i16', 'i32', 'i64', 'f32', 'f64'])
Chandler Carruth7f3facb2014-08-07 04:13:51 +000053
Chandler Carruth8edd4972014-08-13 09:05:59 +000054 element_modulus = {
55 'i8': 1 << 8, 'i16': 1 << 16, 'i32': 1 << 32, 'i64': 1 << 64,
56 'f32': 1 << 32, 'f64': 1 << 64}[element_type]
Chandler Carruth7f3facb2014-08-07 04:13:51 +000057
Chandler Carruth8edd4972014-08-13 09:05:59 +000058 shuffle_range = (2 * width) if args.blends else width
59 shuffle_indices = [-1] + range(shuffle_range)
Chandler Carruth7f3facb2014-08-07 04:13:51 +000060
Chandler Carruth8edd4972014-08-13 09:05:59 +000061 shuffle_tree = [[[random.choice(shuffle_indices)
62 for _ in itertools.repeat(None, width)]
63 for _ in itertools.repeat(None, args.max_shuffle_height - i)]
64 for i in xrange(args.max_shuffle_height)]
Chandler Carruth7f3facb2014-08-07 04:13:51 +000065
66 if args.verbose:
67 # Print out the shuffle sequence in a compact form.
Chandler Carruth8edd4972014-08-13 09:05:59 +000068 print >>sys.stderr, ('Testing shuffle sequence "%s" (v%d%s):' %
69 (args.seed, width, element_type))
70 for i, shuffles in enumerate(shuffle_tree):
71 print >>sys.stderr, ' tree level %d:' % (i,)
72 for j, s in enumerate(shuffles):
73 print >>sys.stderr, ' shuffle %d: %s' % (j, s)
Chandler Carruth7f3facb2014-08-07 04:13:51 +000074 print >>sys.stderr, ''
75
Chandler Carruth8edd4972014-08-13 09:05:59 +000076 # Symbolically evaluate the shuffle tree.
77 inputs = [[int(j % element_modulus)
78 for j in xrange(i * width + 1, (i + 1) * width + 1)]
79 for i in xrange(args.max_shuffle_height + 1)]
80 results = inputs
81 for shuffles in shuffle_tree:
82 results = [[((results[i] if j < width else results[i + 1])[j % width]
83 if j != -1 else -1)
84 for j in s]
85 for i, s in enumerate(shuffles)]
86 if len(results) != 1:
87 print >>sys.stderr, 'ERROR: Bad results: %s' % (results,)
88 sys.exit(1)
89 result = results[0]
Chandler Carruth7f3facb2014-08-07 04:13:51 +000090
91 if args.verbose:
92 print >>sys.stderr, 'Which transforms:'
Chandler Carruth8edd4972014-08-13 09:05:59 +000093 print >>sys.stderr, ' from: %s' % (inputs,)
Chandler Carruth7f3facb2014-08-07 04:13:51 +000094 print >>sys.stderr, ' into: %s' % (result,)
95 print >>sys.stderr, ''
96
97 # The IR uses silly names for floating point types. We also need a same-size
98 # integer type.
99 integral_element_type = element_type
100 if element_type == 'f32':
101 integral_element_type = 'i32'
102 element_type = 'float'
103 elif element_type == 'f64':
104 integral_element_type = 'i64'
105 element_type = 'double'
106
107 # Now we need to generate IR for the shuffle function.
108 subst = {'N': width, 'T': element_type, 'IT': integral_element_type}
109 print """
Chandler Carruth8edd4972014-08-13 09:05:59 +0000110define internal fastcc <%(N)d x %(T)s> @test(%(arguments)s) noinline nounwind {
111entry:""" % dict(subst,
112 arguments=', '.join(
113 ['<%(N)d x %(T)s> %%s.0.%(i)d' % dict(subst, i=i)
114 for i in xrange(args.max_shuffle_height + 1)]))
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000115
Chandler Carruth8edd4972014-08-13 09:05:59 +0000116 for i, shuffles in enumerate(shuffle_tree):
117 for j, s in enumerate(shuffles):
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000118 print """
Chandler Carruth8edd4972014-08-13 09:05:59 +0000119 %%s.%(next_i)d.%(j)d = shufflevector <%(N)d x %(T)s> %%s.%(i)d.%(j)d, <%(N)d x %(T)s> %%s.%(i)d.%(next_j)d, <%(N)d x i32> <%(S)s>
120""".strip('\n') % dict(subst, i=i, next_i=i + 1, j=j, next_j=j + 1,
121 S=', '.join(['i32 ' + (str(si) if si != -1 else 'undef')
122 for si in s]))
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000123
124 print """
Chandler Carruth8edd4972014-08-13 09:05:59 +0000125 ret <%(N)d x %(T)s> %%s.%(i)d.0
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000126}
Chandler Carruth8edd4972014-08-13 09:05:59 +0000127""" % dict(subst, i=len(shuffle_tree))
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000128
129 # Generate some string constants that we can use to report errors.
130 for i, r in enumerate(result):
131 if r != -1:
132 s = ('FAIL(%(seed)s): lane %(lane)d, expected %(result)d, found %%d\\0A' %
133 {'seed': args.seed, 'lane': i, 'result': r})
Chandler Carruthf7b06d62014-08-13 03:21:11 +0000134 s += ''.join(['\\00' for _ in itertools.repeat(None, 128 - len(s) + 2)])
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000135 print """
Chandler Carruthf7b06d62014-08-13 03:21:11 +0000136@error.%(i)d = private unnamed_addr global [128 x i8] c"%(s)s"
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000137""".strip() % {'i': i, 's': s}
138
Chandler Carruth8edd4972014-08-13 09:05:59 +0000139 # Define a wrapper function which is marked 'optnone' to prevent
140 # interprocedural optimizations from deleting the test.
141 print """
142define internal fastcc <%(N)d x %(T)s> @test_wrapper(%(arguments)s) optnone noinline {
143 %%result = call fastcc <%(N)d x %(T)s> @test(%(arguments)s)
144 ret <%(N)d x %(T)s> %%result
145}
146""" % dict(subst,
147 arguments=', '.join(['<%(N)d x %(T)s> %%s.%(i)d' % dict(subst, i=i)
148 for i in xrange(args.max_shuffle_height + 1)]))
149
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000150 # Finally, generate a main function which will trap if any lanes are mapped
151 # incorrectly (in an observable way).
152 print """
Chandler Carruth8edd4972014-08-13 09:05:59 +0000153define i32 @main() {
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000154entry:
155 ; Create a scratch space to print error messages.
Chandler Carruthf7b06d62014-08-13 03:21:11 +0000156 %%str = alloca [128 x i8]
157 %%str.ptr = getelementptr inbounds [128 x i8]* %%str, i32 0, i32 0
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000158
159 ; Build the input vector and call the test function.
Chandler Carruth8edd4972014-08-13 09:05:59 +0000160 %%v = call fastcc <%(N)d x %(T)s> @test_wrapper(%(inputs)s)
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000161 ; We need to cast this back to an integer type vector to easily check the
162 ; result.
163 %%v.cast = bitcast <%(N)d x %(T)s> %%v to <%(N)d x %(IT)s>
164 br label %%test.0
165""" % dict(subst,
Chandler Carruth8edd4972014-08-13 09:05:59 +0000166 inputs=', '.join(
167 [('<%(N)d x %(T)s> bitcast '
168 '(<%(N)d x %(IT)s> <%(input)s> to <%(N)d x %(T)s>)' %
169 dict(subst, input=', '.join(['%(IT)s %(i)d' % dict(subst, i=i)
170 for i in input])))
171 for input in inputs]))
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000172
173 # Test that each non-undef result lane contains the expected value.
174 for i, r in enumerate(result):
175 if r == -1:
176 print """
177test.%(i)d:
178 ; Skip this lane, its value is undef.
179 br label %%test.%(next_i)d
180""" % dict(subst, i=i, next_i=i + 1)
181 else:
182 print """
183test.%(i)d:
184 %%v.%(i)d = extractelement <%(N)d x %(IT)s> %%v.cast, i32 %(i)d
185 %%cmp.%(i)d = icmp ne %(IT)s %%v.%(i)d, %(r)d
186 br i1 %%cmp.%(i)d, label %%die.%(i)d, label %%test.%(next_i)d
187
188die.%(i)d:
189 ; Capture the actual value and print an error message.
190 %%tmp.%(i)d = zext %(IT)s %%v.%(i)d to i2048
191 %%bad.%(i)d = trunc i2048 %%tmp.%(i)d to i32
Chandler Carruthf7b06d62014-08-13 03:21:11 +0000192 call i32 (i8*, i8*, ...)* @sprintf(i8* %%str.ptr, i8* getelementptr inbounds ([128 x i8]* @error.%(i)d, i32 0, i32 0), i32 %%bad.%(i)d)
Chandler Carruth7f3facb2014-08-07 04:13:51 +0000193 %%length.%(i)d = call i32 @strlen(i8* %%str.ptr)
194 %%size.%(i)d = add i32 %%length.%(i)d, 1
195 call i32 @write(i32 2, i8* %%str.ptr, i32 %%size.%(i)d)
196 call void @llvm.trap()
197 unreachable
198""" % dict(subst, i=i, next_i=i + 1, r=r)
199
200 print """
201test.%d:
202 ret i32 0
203}
204
205declare i32 @strlen(i8*)
206declare i32 @write(i32, i8*, i32)
207declare i32 @sprintf(i8*, i8*, ...)
208declare void @llvm.trap() noreturn nounwind
209""" % (len(result),)
210
211if __name__ == '__main__':
212 main()