blob: 6851f12341f7745718c0ca4cc155acc7e02b535a [file] [edit]
#!/usr/bin/python2.6
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
LICENSE = (
"/*",
" * Copyright (c) 2011 The Native Client Authors. All rights reserved.",
" * Use of this source code is governed by a BSD-style license that can be",
" * found in the LICENSE file.",
" */")
#############################################################################
#
# This script generates the calling_conv test modules.
#
# The following features are checked by this test:
#
# 1) Variable arguments
# 2) Argument alignments
# 3) Large numbers of arguments
# (on X86-64, this tests the overflow_area mechanism)
# 4) Passing va_list between functions.
# 5) va_copy
#
# To generate the test, just run: ./generate.py
#
#############################################################################
import random
import string
import sys
def GenerateTypeInfo(settings):
type_info = [
# id/cast format Maker CompareExpr AssignStmt
#########################################################################
# standard types
('t_charp', 'C', make_string , '%s == %s' , '%s = %s;' ),
('t_int', 'i', make_int , '%s == %s' , '%s = %s;' ),
('t_long', 'l', make_long , '%s == %s' , '%s = %s;' ),
('t_llong', 'L', make_llong , '%s == %s' , '%s = %s;' ),
('t_double', 'd', make_double , '%s == %s' , '%s = %s;' ),
('t_ldouble', 'D', make_ldouble, '%s == %s' , '%s = %s;' ),
('t_char', 'c', make_char , '%s == %s' , '%s = %s;' ),
('t_short', 's', make_short , '%s == %s' , '%s = %s;' ),
('t_float', 'f', make_float , '%s == %s' , '%s = %s;' ),
# custom types
('t_tiny', 'T', make_tiny, 'TINY_CMP(%s,%s)', 'SET_TINY(%s, %s);' ),
('t_big', 'B', make_big, 'BIG_CMP(%s,%s)', 'SET_BIG(%s, %s);' ),
]
# These types cannot be used directly in va_arg().
va_arg_exclude = [ 't_char', 't_short', 't_float' ]
all_exclude = []
if not settings.allow_struct:
all_exclude += [ 't_tiny', 't_big' ]
if not settings.allow_double:
all_exclude += [ 't_double', 't_ldouble' ]
if not settings.allow_float:
all_exclude += [ 't_float']
if not settings.allow_struct_va_arg:
va_arg_exclude += [ 't_tiny', 't_big' ]
settings.all_types = [ CType(*args) for args in type_info
if args[0] not in all_exclude]
settings.va_arg_types = [ t for t in settings.all_types
if t.id not in va_arg_exclude ]
# See also the generated comments in the generated .c files for the settings.
print 'Generating with normal arg types: %s' % str(settings.all_types)
print 'Generating with var arg types: %s' % str(settings.va_arg_types)
class CType(object):
def __init__(self, id, format, maker, compare_expr, assign_stmt):
self.id = id
self.format = format
self.maker = maker
self.compare_expr = compare_expr
self.assign_stmt = assign_stmt
def __repr__(self):
return self.id
class Settings(object):
def __init__(self):
# If the seed is not specified on the command line, choose a random value.
self.seed = random.getrandbits(64)
self.num_functions = 0
self.calls_per_func = 0
self.max_args_per_func = 0
self.num_modules = 0
self.allow_struct = 1
self.allow_double = 1
self.allow_float = 1
self.allow_struct_va_arg = 1
self._script_argv = None
def fixtypes(self):
self.seed = long(self.seed)
self.num_functions = int(self.num_functions)
self.calls_per_func = int(self.calls_per_func)
self.max_args_per_func = int(self.max_args_per_func)
self.num_modules = int(self.num_modules)
self.allow_struct = bool(int(self.allow_struct))
self.allow_double = bool(int(self.allow_double))
self.allow_float = bool(int(self.allow_float))
self.allow_struct_va_arg = bool(int(self.allow_struct_va_arg))
def set(self, k, v):
if not hasattr(self, k):
return False
setattr(self, k, v)
return True
def Usage():
print "Usage: %s <options> --" % sys.argv[0],
print "<golden_output_filename> <module0_filename> <module1_filename> ..."
print
print "Valid options are:"
print " --seed=<64-bit-random-seed>"
print " --num_functions=<num_functions>"
print " --calls_per_func=<calls_per_function>"
print " --max_args_per_func=<max_args_per_function>"
print " --num_modules=<num_modules>"
print " --allow_struct=<0|1> Test struct arguments (by value)"
print " --allow_double=<0|1> Test double arguments (by value)"
print " --allow_float=<0|1> Test float arguments (by value)"
print " --allow_struct_va_arg=<0|1> Test va_arg on struct arguments"
sys.exit(1)
def main(argv):
if len(argv) == 0:
Usage()
settings = Settings()
for i, arg in enumerate(argv):
if arg == '--':
break
if arg.startswith('--'):
k,v = arg[2:].split('=')
if not settings.set(k,v):
Fatal("Unknown setting: %s", k)
else:
Usage()
settings.fixtypes()
settings._script_argv = ' '.join(argv)
# The parameters after "--" are the golden output file, followed
# by the modules files
if (len(argv) - i - 1) != 1+settings.num_modules:
Fatal("Incorrect number of parameters after --")
output_golden_file = argv[i+1]
output_module_files = argv[i+2:]
GenerateTypeInfo(settings)
functions, calls, num_asserts = GenerateTest(settings)
# Randomly separate the calls and functions among the modules
functions_for_module = randsplit(functions, settings.num_modules)
calls_for_module = randsplit(calls, settings.num_modules)
for m in xrange(settings.num_modules):
fp = WriteBuffer(open(output_module_files[m], 'w'))
m = Module(settings, m, functions_for_module[m], calls_for_module[m],
functions)
m.emit(fp)
fp.close()
print "callingconv seed: %d" % settings.seed
fp = open(output_golden_file, 'w')
fp.write("generate.py arguments: %s\n" % settings._script_argv)
fp.write("SUCCESS: %d calls OK.\n" % num_asserts)
fp.close()
return 0
def Fatal(m, *args):
if len(args) > 0:
m = m % args
print m
sys.exit(2)
class WriteBuffer(object):
def __init__(self, fp):
self.fp = fp
self.buffer = ''
def write(self, data):
self.buffer += data
def flush(self):
self.fp.write(self.buffer)
self.buffer = ''
def close(self):
self.flush()
self.fp.close()
def randsplit(inlist, n):
""" Randomly split a list into n sublists. """
inlist = list(inlist)
random.shuffle(inlist)
selections = [ (random.randint(0, n-1), m) for m in inlist ]
sublists = [ [] for i in xrange(n) ]
for (i, m) in selections:
sublists[i].append(m)
return sublists
class Module(object):
def __init__(self, settings, id, functions, calls, all_functions):
self.settings = settings
self.id = id
self.calls = calls
self.functions = functions
self.all_functions = all_functions
def emit(self, out):
out.write('\n'.join(LICENSE))
out.write('\n')
out.write("/*--------------------------------------------------*\n"
" * THIS FILE IS AUTO-GENERATED *\n"
" * DO NOT MODIFY THIS FILE *\n"
" * MODIFY 'generate.py' instead *\n"
" *--------------------------------------------------*/\n")
out.write("\n")
# only emit this for the first module we generate
if self.id == 0:
out.write("const char *script_argv =\n")
out.write(" \"" + CEscape(self.settings._script_argv) + "\";\n")
out.write("/*--------------------------------------------------*\n"
" * Generator Settings: *\n")
for k,v in self.settings.__dict__.iteritems():
if k.startswith('_'):
continue
lines = WordWrap(str(v), 22)
for i, s in enumerate(lines):
if i == 0:
out.write(" * %-21s = %-22s *\n" % (k, s))
else:
out.write(" * %-21s %-22s *\n" % ('', s))
out.write(" *--------------------------------------------------*/\n")
out.write("\n")
out.write('#include <stdio.h>\n')
out.write('#include <stdlib.h>\n')
out.write('#include <string.h>\n')
out.write('#include <stdarg.h>\n')
out.write('#include <callingconv.h>\n')
out.write("\n")
# Emit function prototypes (all of them)
for f in self.all_functions:
f.emit_prototype(out)
out.write("\n")
# Emit prototypes of the vcheck functions
for module_id in xrange(self.settings.num_modules):
out.write("void vcheck%d(va_list ap, int i, char type);\n" % module_id)
out.write("\n")
# Emit the main module function
out.write("void module%d(void) {\n" % self.id)
out.write(" SET_CURRENT_MODULE(%d);\n" % self.id)
out.write("\n")
for c in self.calls:
c.emit(out)
out.write("}\n\n")
# Emit the "vcheck" function, for checking
# va_list passing.
out.write("void vcheck%d(va_list ap, int i, char type) {\n" % self.id)
for t in self.settings.va_arg_types:
va_arg_val = "va_arg(ap, %s)" % t.id
expected_val = "v_%s[i]" % t.id
comparison = t.compare_expr % (va_arg_val, expected_val)
out.write(" if (type == '%s')\n" % t.format)
out.write(" ASSERT(%s);\n" % comparison)
out.write("")
out.write("}\n\n")
# Emit the function definitions in this module
for f in self.functions:
f.emit(out)
out.write("\n")
def CEscape(s):
s = s.replace('\\', '\\\\')
s = s.replace('"', '\\"')
return s
def WordWrap(s, linelen):
words = s.split(' ')
lines = []
line = ''
spaces = 0
for w in words:
if len(line) + spaces + len(w) > linelen:
lines.append(line)
line = ''
spaces = 0
line += (' ' * spaces) + w
spaces = 1
lines.append(line)
return lines
def GenerateTest(settings):
random.seed(settings.seed)
functions = [ TestFunction(settings, i)
for i in xrange(settings.num_functions) ]
calls = [ ]
callcount = 0
for f in functions:
for i in xrange(settings.calls_per_func):
calls.append(TestCall(settings, callcount, f))
callcount += 1
num_asserts = sum([ c.num_asserts for c in calls ])
return (functions, calls, num_asserts)
class TestCall(object):
def __init__(self, settings, call_id, function):
self.settings = settings
self.id = call_id
self.f = function
self.num_var_args = random.randint(0, self.settings.max_args_per_func -
self.f.num_fixed_args)
# Generate argument inputs
self.num_args = self.f.num_fixed_args + self.num_var_args
args = []
fmtstr = ''
for i in xrange(self.num_args):
if i < self.f.num_fixed_args:
t = self.f.fixed_arg_types[i]
else:
t = random.choice(settings.va_arg_types)
if i == self.f.num_fixed_args:
fmtstr += '" "'
fmtstr += str(t.format)
args.append((i, t, t.maker()))
self.args = args
self.fmtstr = fmtstr
# Each fixed argument is checked once.
# Each variable argument is checked once by the test function,
# and once by each vcheck() call. (one per module)
self.num_asserts = (self.f.num_fixed_args +
self.num_var_args * (settings.num_modules + 1))
def emit(self, out):
out.write(" /* C%d */\n" % self.id)
out.write(" SET_CURRENT_CALL(%d);\n" % self.id)
# Set the check variables
for (i, t, value) in self.args:
var = "v_%s[%d]" % (t.id, i)
setexpr = t.assign_stmt % (var, value)
out.write(prettycode(' ' + setexpr) + '\n')
# Call the function
argstr = ''
for (i, t, value) in self.args:
argstr += ', v_%s[%d]' % (t.id, i)
callstr = ' F%s("%s"%s);\n' % (self.f.id, self.fmtstr, argstr)
out.write(prettycode(callstr))
out.write("\n")
def prettycode(str):
"""Try to beautify a line of code."""
if len(str) < 80:
return str
lparen = str.find('(')
rparen = str.rfind(')')
if lparen < 0 or rparen < 0:
Fatal("Invalid code string")
head = str[ 0 : lparen ]
inner = str[ lparen+1 : rparen ]
tail = str[ rparen+1 : ]
args = inner.split(",")
ret = head + "("
pos = len(ret)
indent = len(ret)
firstarg = True
for arg in args:
if firstarg:
s = arg.strip()
firstarg = False
elif pos + len(arg) < 75:
s = ", " + arg.strip()
else:
ret += ",\n" + (' '*indent)
pos = indent
s = arg.strip()
ret += s
pos += len(s)
ret += ")" + tail
return ret
# Represents a randomly generated test function
class TestFunction(object):
def __init__(self, settings, id):
self.settings = settings
self.id = id
self.num_fixed_args = random.randint(0, self.settings.max_args_per_func)
self.fixed_arg_types = range(self.num_fixed_args)
for i in xrange(len(self.fixed_arg_types)):
self.fixed_arg_types[i] = random.choice(settings.all_types)
def emit_prototype(self, out, is_def = False):
fixed_arg_str = ''
argnum = 0
for t in self.fixed_arg_types:
fixed_arg_str += ", %s a_%d" % (t.id, argnum)
argnum += 1
prototype = "void F%s(const char *fmt%s, ...)" % (self.id, fixed_arg_str)
out.write(prettycode(prototype))
if is_def:
out.write(" {\n")
else:
out.write(";\n")
out.write("\n")
def emit(self, out):
self.emit_prototype(out, True)
out.write(" va_list ap;\n")
out.write(" va_list ap2;\n")
out.write(" int i = 0;\n")
out.write("\n")
out.write(" SET_CURRENT_FUNCTION(%d);\n" % self.id)
out.write(" SET_INDEX_VARIABLE(i);\n")
out.write("\n")
if self.num_fixed_args > 0:
out.write(" /* Handle fixed arguments */\n")
for (i, t) in enumerate(self.fixed_arg_types):
arg_val = "a_%d" % i
check_val = "v_%s[i]" % (t.id)
comp_expr = t.compare_expr % (arg_val, check_val)
out.write(" ASSERT(%s); i++;\n" % (comp_expr,))
out.write("\n")
# The last fixed argument
last_arg = arg_val
else:
last_arg = "fmt"
# Handle variable arguments
out.write(" /* Handle variable arguments */\n")
out.write(" va_start(ap, %s);\n" % last_arg)
out.write(" while (fmt[i]) {\n")
out.write(" switch (fmt[i]) {\n")
for t in self.settings.va_arg_types:
arg_val = "va_arg(ap, %s)" % t.id
check_val = "v_%s[i]" % t.id
comp_expr = t.compare_expr % (arg_val, check_val)
out.write(" case '%s':\n" % t.format)
for module_id in xrange(self.settings.num_modules):
out.write(" va_copy(ap2, ap);\n")
out.write(" vcheck%d(ap2, i, fmt[i]);\n" % module_id)
out.write(" va_end(ap2);\n");
out.write(" ASSERT(%s);\n" % comp_expr)
out.write(" break;\n")
out.write(" default:\n")
out.write(' printf("Unexpected type code\\n");\n')
out.write(" exit(1);\n")
out.write(" }\n")
out.write(" i++;\n")
out.write(" }\n")
out.write(" va_end(ap);\n")
out.write("}\n\n")
alphabet = string.letters + string.digits
def make_string():
global alphabet
randstr = [random.choice(alphabet) for i in xrange(random.randint(0,16))]
randstr = ''.join(randstr)
return '"%s"' % randstr
def make_int():
B = pow(2,31)
return str(random.randint(-B, B-1))
def make_long():
B = pow(2,31)
return str(random.randint(-B, B-1)) + "L"
def make_llong():
B = pow(2,63)
return str(random.randint(-B, B-1)) + "LL"
def make_float():
return str(random.random() * pow(2.0, random.randint(-126, 127)))
def make_double():
return str( random.random() * pow(2.0, random.randint(-1022, 1023)) )
def make_ldouble():
return "((long double)%s)" % make_double()
def make_char():
global alphabet
return "'%s'" % random.choice(alphabet)
def make_short():
B = pow(2,15)
return str(random.randint(-B, B-1))
def make_tiny():
a = make_char()
b = make_short()
return "%s,%s" % (a,b)
def make_big():
a = make_char()
b = make_char()
c = make_int()
d = make_char()
e = make_int()
f = make_char()
g = make_llong()
h = make_char()
i = make_int()
j = make_char()
k = make_short()
l = make_char()
m = make_double()
n = make_char()
return "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % \
(a,b,c,d,e,f,g,h,i,j,k,l,m,n)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))