mirror of
				https://github.com/c64scene-ar/llvm-6502.git
				synced 2025-10-30 16:17:05 +00:00 
			
		
		
		
	more useful output. No more null bytes printed, and now with a newline. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@229599 91177308-0d34-0410-b5e6-96231b3b80d8
		
			
				
	
	
		
			256 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| """A shuffle vector fuzz tester.
 | |
| 
 | |
| This is a python program to fuzz test the LLVM shufflevector instruction. It
 | |
| generates a function with a random sequnece of shufflevectors, 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.
 | |
| """
 | |
| 
 | |
| import argparse
 | |
| import itertools
 | |
| import random
 | |
| import sys
 | |
| import uuid
 | |
| 
 | |
| def main():
 | |
|   element_types=['i8', 'i16', 'i32', 'i64', 'f32', 'f64']
 | |
|   parser = argparse.ArgumentParser(description=__doc__)
 | |
|   parser.add_argument('-v', '--verbose', action='store_true',
 | |
|                       help='Show verbose output')
 | |
|   parser.add_argument('--seed', default=str(uuid.uuid4()),
 | |
|                       help='A string used to seed the RNG')
 | |
|   parser.add_argument('--max-shuffle-height', type=int, default=16,
 | |
|                       help='Specify a fixed height of shuffle tree to test')
 | |
|   parser.add_argument('--no-blends', dest='blends', action='store_false',
 | |
|                       help='Include blends of two input vectors')
 | |
|   parser.add_argument('--fixed-bit-width', type=int, choices=[128, 256],
 | |
|                       help='Specify a fixed bit width of vector to test')
 | |
|   parser.add_argument('--fixed-element-type', choices=element_types,
 | |
|                       help='Specify a fixed element type to test')
 | |
|   parser.add_argument('--triple',
 | |
|                       help='Specify a triple string to include in the IR')
 | |
|   args = parser.parse_args()
 | |
| 
 | |
|   random.seed(args.seed)
 | |
| 
 | |
|   if args.fixed_element_type is not None:
 | |
|     element_types=[args.fixed_element_type]
 | |
| 
 | |
|   if args.fixed_bit_width is not None:
 | |
|     if args.fixed_bit_width == 128:
 | |
|       width_map={'i64': 2, 'i32': 4, 'i16': 8, 'i8': 16, 'f64': 2, 'f32': 4}
 | |
|       (width, element_type) = random.choice(
 | |
|           [(width_map[t], t) for t in element_types])
 | |
|     elif args.fixed_bit_width == 256:
 | |
|       width_map={'i64': 4, 'i32': 8, 'i16': 16, 'i8': 32, 'f64': 4, 'f32': 8}
 | |
|       (width, element_type) = random.choice(
 | |
|           [(width_map[t], t) for t in element_types])
 | |
|     else:
 | |
|       sys.exit(1) # Checked above by argument parsing.
 | |
|   else:
 | |
|     width = random.choice([2, 4, 8, 16, 32, 64])
 | |
|     element_type = random.choice(element_types)
 | |
| 
 | |
|   element_modulus = {
 | |
|       'i8': 1 << 8, 'i16': 1 << 16, 'i32': 1 << 32, 'i64': 1 << 64,
 | |
|       'f32': 1 << 32, 'f64': 1 << 64}[element_type]
 | |
| 
 | |
|   shuffle_range = (2 * width) if args.blends else width
 | |
| 
 | |
|   # Because undef (-1) saturates and is indistinguishable when testing the
 | |
|   # correctness of a shuffle, we want to bias our fuzz toward having a decent
 | |
|   # mixture of non-undef lanes in the end. With a deep shuffle tree, the
 | |
|   # probabilies aren't good so we need to bias things. The math here is that if
 | |
|   # we uniformly select between -1 and the other inputs, each element of the
 | |
|   # result will have the following probability of being undef:
 | |
|   #
 | |
|   #   1 - (shuffle_range/(shuffle_range+1))^max_shuffle_height
 | |
|   #
 | |
|   # More generally, for any probability P of selecting a defined element in
 | |
|   # a single shuffle, the end result is:
 | |
|   #
 | |
|   #   1 - P^max_shuffle_height
 | |
|   #
 | |
|   # The power of the shuffle height is the real problem, as we want:
 | |
|   #
 | |
|   #   1 - shuffle_range/(shuffle_range+1)
 | |
|   #
 | |
|   # So we bias the selection of undef at any given node based on the tree
 | |
|   # height. Below, let 'A' be 'len(shuffle_range)', 'C' be 'max_shuffle_height',
 | |
|   # and 'B' be the bias we use to compensate for
 | |
|   # C '((A+1)*A^(1/C))/(A*(A+1)^(1/C))':
 | |
|   #
 | |
|   #   1 - (B * A)/(A + 1)^C = 1 - A/(A + 1)
 | |
|   #
 | |
|   # So at each node we use:
 | |
|   #
 | |
|   #   1 - (B * A)/(A + 1)
 | |
|   # = 1 - ((A + 1) * A * A^(1/C))/(A * (A + 1) * (A + 1)^(1/C))
 | |
|   # = 1 - ((A + 1) * A^((C + 1)/C))/(A * (A + 1)^((C + 1)/C))
 | |
|   #
 | |
|   # This is the formula we use to select undef lanes in the shuffle.
 | |
|   A = float(shuffle_range)
 | |
|   C = float(args.max_shuffle_height)
 | |
|   undef_prob = 1.0 - (((A + 1.0) * pow(A, (C + 1.0)/C)) /
 | |
|                       (A * pow(A + 1.0, (C + 1.0)/C)))
 | |
| 
 | |
|   shuffle_tree = [[[-1 if random.random() <= undef_prob
 | |
|                        else random.choice(range(shuffle_range))
 | |
|                     for _ in itertools.repeat(None, width)]
 | |
|                    for _ in itertools.repeat(None, args.max_shuffle_height - i)]
 | |
|                   for i in xrange(args.max_shuffle_height)]
 | |
| 
 | |
|   if args.verbose:
 | |
|     # Print out the shuffle sequence in a compact form.
 | |
|     print >>sys.stderr, ('Testing shuffle sequence "%s" (v%d%s):' %
 | |
|                          (args.seed, width, element_type))
 | |
|     for i, shuffles in enumerate(shuffle_tree):
 | |
|       print >>sys.stderr, '  tree level %d:' % (i,)
 | |
|       for j, s in enumerate(shuffles):
 | |
|         print >>sys.stderr, '    shuffle %d: %s' % (j, s)
 | |
|     print >>sys.stderr, ''
 | |
| 
 | |
|   # Symbolically evaluate the shuffle tree.
 | |
|   inputs = [[int(j % element_modulus)
 | |
|              for j in xrange(i * width + 1, (i + 1) * width + 1)]
 | |
|             for i in xrange(args.max_shuffle_height + 1)]
 | |
|   results = inputs
 | |
|   for shuffles in shuffle_tree:
 | |
|     results = [[((results[i] if j < width else results[i + 1])[j % width]
 | |
|                  if j != -1 else -1)
 | |
|                 for j in s]
 | |
|                for i, s in enumerate(shuffles)]
 | |
|   if len(results) != 1:
 | |
|     print >>sys.stderr, 'ERROR: Bad results: %s' % (results,)
 | |
|     sys.exit(1)
 | |
|   result = results[0]
 | |
| 
 | |
|   if args.verbose:
 | |
|     print >>sys.stderr, 'Which transforms:'
 | |
|     print >>sys.stderr, '  from: %s' % (inputs,)
 | |
|     print >>sys.stderr, '  into: %s' % (result,)
 | |
|     print >>sys.stderr, ''
 | |
| 
 | |
|   # The IR uses silly names for floating point types. We also need a same-size
 | |
|   # integer type.
 | |
|   integral_element_type = element_type
 | |
|   if element_type == 'f32':
 | |
|     integral_element_type = 'i32'
 | |
|     element_type = 'float'
 | |
|   elif element_type == 'f64':
 | |
|     integral_element_type = 'i64'
 | |
|     element_type = 'double'
 | |
| 
 | |
|   # Now we need to generate IR for the shuffle function.
 | |
|   subst = {'N': width, 'T': element_type, 'IT': integral_element_type}
 | |
|   print """
 | |
| define internal fastcc <%(N)d x %(T)s> @test(%(arguments)s) noinline nounwind {
 | |
| entry:""" % dict(subst,
 | |
|                  arguments=', '.join(
 | |
|                      ['<%(N)d x %(T)s> %%s.0.%(i)d' % dict(subst, i=i)
 | |
|                       for i in xrange(args.max_shuffle_height + 1)]))
 | |
| 
 | |
|   for i, shuffles in enumerate(shuffle_tree):
 | |
|    for j, s in enumerate(shuffles):
 | |
|     print """
 | |
|   %%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>
 | |
| """.strip('\n') % dict(subst, i=i, next_i=i + 1, j=j, next_j=j + 1,
 | |
|                        S=', '.join(['i32 ' + (str(si) if si != -1 else 'undef')
 | |
|                                     for si in s]))
 | |
| 
 | |
|   print """
 | |
|   ret <%(N)d x %(T)s> %%s.%(i)d.0
 | |
| }
 | |
| """ % dict(subst, i=len(shuffle_tree))
 | |
| 
 | |
|   # Generate some string constants that we can use to report errors.
 | |
|   for i, r in enumerate(result):
 | |
|     if r != -1:
 | |
|       s = ('FAIL(%(seed)s): lane %(lane)d, expected %(result)d, found %%d\n\\0A' %
 | |
|            {'seed': args.seed, 'lane': i, 'result': r})
 | |
|       s += ''.join(['\\00' for _ in itertools.repeat(None, 128 - len(s) + 2)])
 | |
|       print """
 | |
| @error.%(i)d = private unnamed_addr global [128 x i8] c"%(s)s"
 | |
| """.strip() % {'i': i, 's': s}
 | |
| 
 | |
|   # Define a wrapper function which is marked 'optnone' to prevent
 | |
|   # interprocedural optimizations from deleting the test.
 | |
|   print """
 | |
| define internal fastcc <%(N)d x %(T)s> @test_wrapper(%(arguments)s) optnone noinline {
 | |
|   %%result = call fastcc <%(N)d x %(T)s> @test(%(arguments)s)
 | |
|   ret <%(N)d x %(T)s> %%result
 | |
| }
 | |
| """ % dict(subst,
 | |
|            arguments=', '.join(['<%(N)d x %(T)s> %%s.%(i)d' % dict(subst, i=i)
 | |
|                                 for i in xrange(args.max_shuffle_height + 1)]))
 | |
| 
 | |
|   # Finally, generate a main function which will trap if any lanes are mapped
 | |
|   # incorrectly (in an observable way).
 | |
|   print """
 | |
| define i32 @main() {
 | |
| entry:
 | |
|   ; Create a scratch space to print error messages.
 | |
|   %%str = alloca [128 x i8]
 | |
|   %%str.ptr = getelementptr inbounds [128 x i8]* %%str, i32 0, i32 0
 | |
| 
 | |
|   ; Build the input vector and call the test function.
 | |
|   %%v = call fastcc <%(N)d x %(T)s> @test_wrapper(%(inputs)s)
 | |
|   ; We need to cast this back to an integer type vector to easily check the
 | |
|   ; result.
 | |
|   %%v.cast = bitcast <%(N)d x %(T)s> %%v to <%(N)d x %(IT)s>
 | |
|   br label %%test.0
 | |
| """ % dict(subst,
 | |
|            inputs=', '.join(
 | |
|                [('<%(N)d x %(T)s> bitcast '
 | |
|                  '(<%(N)d x %(IT)s> <%(input)s> to <%(N)d x %(T)s>)' %
 | |
|                  dict(subst, input=', '.join(['%(IT)s %(i)d' % dict(subst, i=i)
 | |
|                                               for i in input])))
 | |
|                 for input in inputs]))
 | |
| 
 | |
|   # Test that each non-undef result lane contains the expected value.
 | |
|   for i, r in enumerate(result):
 | |
|     if r == -1:
 | |
|       print """
 | |
| test.%(i)d:
 | |
|   ; Skip this lane, its value is undef.
 | |
|   br label %%test.%(next_i)d
 | |
| """ % dict(subst, i=i, next_i=i + 1)
 | |
|     else:
 | |
|       print """
 | |
| test.%(i)d:
 | |
|   %%v.%(i)d = extractelement <%(N)d x %(IT)s> %%v.cast, i32 %(i)d
 | |
|   %%cmp.%(i)d = icmp ne %(IT)s %%v.%(i)d, %(r)d
 | |
|   br i1 %%cmp.%(i)d, label %%die.%(i)d, label %%test.%(next_i)d
 | |
| 
 | |
| die.%(i)d:
 | |
|   ; Capture the actual value and print an error message.
 | |
|   %%tmp.%(i)d = zext %(IT)s %%v.%(i)d to i2048
 | |
|   %%bad.%(i)d = trunc i2048 %%tmp.%(i)d to i32
 | |
|   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)
 | |
|   %%length.%(i)d = call i32 @strlen(i8* %%str.ptr)
 | |
|   call i32 @write(i32 2, i8* %%str.ptr, i32 %%length.%(i)d)
 | |
|   call void @llvm.trap()
 | |
|   unreachable
 | |
| """ % dict(subst, i=i, next_i=i + 1, r=r)
 | |
| 
 | |
|   print """
 | |
| test.%d:
 | |
|   ret i32 0
 | |
| }
 | |
| 
 | |
| declare i32 @strlen(i8*)
 | |
| declare i32 @write(i32, i8*, i32)
 | |
| declare i32 @sprintf(i8*, i8*, ...)
 | |
| declare void @llvm.trap() noreturn nounwind
 | |
| """ % (len(result),)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|   main()
 |