1#!/usr/bin/python2.6
2# Copyright (c) 2012 The Native Client Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6from __future__ import print_function
7
8LICENSE = (
9  "/*",
10  " * Copyright (c) 2011 The Native Client Authors. All rights reserved.",
11  " * Use of this source code is governed by a BSD-style license that can be",
12  " * found in the LICENSE file.",
13  " */")
14
15#############################################################################
16#
17# This script generates the calling_conv test modules.
18#
19# The following features are checked by this test:
20#
21#  1) Variable arguments
22#  2) Argument alignments
23#  3) Large numbers of arguments
24#     (on X86-64, this tests the overflow_area mechanism)
25#  4) Passing va_list between functions.
26#  5) va_copy
27#
28# To generate the test, just run: ./generate.py
29#
30#############################################################################
31
32import random
33import string
34import sys
35
36def GenerateTypeInfo(settings):
37  type_info = [
38    #   id/cast   format  Maker      CompareExpr  AssignStmt
39    #########################################################################
40    # standard types
41    ('t_charp',    'C',  make_string , '%s == %s'  , '%s = %s;'  ),
42    ('t_int',      'i',  make_int    , '%s == %s'  , '%s = %s;'  ),
43    ('t_long',     'l',  make_long   , '%s == %s'  , '%s = %s;'  ),
44    ('t_llong',    'L',  make_llong  , '%s == %s'  , '%s = %s;'  ),
45    ('t_double',   'd',  make_double , '%s == %s'  , '%s = %s;'  ),
46    ('t_ldouble',  'D',  make_ldouble, '%s == %s'  , '%s = %s;'  ),
47    ('t_char',     'c',  make_char   , '%s == %s'  , '%s = %s;'  ),
48    ('t_short',    's',  make_short  , '%s == %s'  , '%s = %s;'  ),
49    ('t_float',    'f',  make_float  , '%s == %s'  , '%s = %s;'  ),
50    # custom types
51    ('t_tiny',     'T',  make_tiny,  'TINY_CMP(%s,%s)', 'SET_TINY(%s, %s);' ),
52    ('t_big',      'B',  make_big,   'BIG_CMP(%s,%s)', 'SET_BIG(%s, %s);'     ),
53  ]
54
55  # These types cannot be used directly in va_arg().
56  va_arg_exclude = [ 't_char', 't_short', 't_float' ]
57  all_exclude = []
58
59  if not settings.allow_struct:
60    all_exclude += [ 't_tiny', 't_big' ]
61
62  if not settings.allow_double:
63    all_exclude += [ 't_double', 't_ldouble' ]
64
65  if not settings.allow_float:
66    all_exclude += [ 't_float']
67
68  if not settings.allow_struct_va_arg:
69    va_arg_exclude += [ 't_tiny', 't_big' ]
70
71  settings.all_types = [ CType(*args) for args in type_info
72                         if args[0] not in all_exclude]
73  settings.va_arg_types = [ t for t in settings.all_types
74                            if t.id not in va_arg_exclude ]
75  # See also the generated comments in the generated .c files for the settings.
76  print('Generating with normal arg types: %s' % str(settings.all_types))
77  print('Generating with var arg types: %s' % str(settings.va_arg_types))
78
79
80
81class CType(object):
82  def __init__(self, id, format, maker, compare_expr, assign_stmt):
83    self.id = id
84    self.format = format
85    self.maker = maker
86    self.compare_expr = compare_expr
87    self.assign_stmt = assign_stmt
88
89  def __repr__(self):
90    return self.id
91
92class Settings(object):
93  def __init__(self):
94    # If the seed is not specified on the command line, choose a random value.
95    self.seed = random.getrandbits(64)
96    self.num_functions = 0
97    self.calls_per_func = 0
98    self.max_args_per_func = 0
99    self.num_modules = 0
100    self.allow_struct = 1
101    self.allow_double = 1
102    self.allow_float = 1
103    self.allow_struct_va_arg = 1
104    self._script_argv = None
105
106  def fixtypes(self):
107    self.seed = long(self.seed)
108    self.num_functions = int(self.num_functions)
109    self.calls_per_func = int(self.calls_per_func)
110    self.max_args_per_func = int(self.max_args_per_func)
111    self.num_modules = int(self.num_modules)
112    self.allow_struct = bool(int(self.allow_struct))
113    self.allow_double = bool(int(self.allow_double))
114    self.allow_float = bool(int(self.allow_float))
115    self.allow_struct_va_arg = bool(int(self.allow_struct_va_arg))
116
117  def set(self, k, v):
118    if not hasattr(self, k):
119      return False
120    setattr(self, k, v)
121    return True
122
123def Usage():
124  print("Usage: %s <options> --" % sys.argv[0], end=' ')
125  print("<golden_output_filename> <module0_filename> <module1_filename> ...")
126  print()
127  print("Valid options are:")
128  print("  --seed=<64-bit-random-seed>")
129  print("  --num_functions=<num_functions>")
130  print("  --calls_per_func=<calls_per_function>")
131  print("  --max_args_per_func=<max_args_per_function>")
132  print("  --num_modules=<num_modules>")
133  print("  --allow_struct=<0|1>         Test struct arguments (by value)")
134  print("  --allow_double=<0|1>         Test double arguments (by value)")
135  print("  --allow_float=<0|1>          Test float arguments (by value)")
136  print("  --allow_struct_va_arg=<0|1>  Test va_arg on struct arguments")
137  sys.exit(1)
138
139def main(argv):
140  if len(argv) == 0:
141    Usage()
142
143  settings = Settings()
144  for i, arg in enumerate(argv):
145    if arg == '--':
146      break
147    if arg.startswith('--'):
148      k,v = arg[2:].split('=')
149      if not settings.set(k,v):
150        Fatal("Unknown setting: %s", k)
151    else:
152      Usage()
153  settings.fixtypes()
154  settings._script_argv = ' '.join(argv)
155
156  # The parameters after "--" are the golden output file, followed
157  # by the modules files
158  if (len(argv) - i - 1) != 1+settings.num_modules:
159    Fatal("Incorrect number of parameters after --")
160  output_golden_file = argv[i+1]
161  output_module_files = argv[i+2:]
162
163  GenerateTypeInfo(settings)
164
165  functions, calls, num_asserts = GenerateTest(settings)
166
167  # Randomly separate the calls and functions among the modules
168  functions_for_module = randsplit(functions, settings.num_modules)
169  calls_for_module = randsplit(calls, settings.num_modules)
170
171  for m in xrange(settings.num_modules):
172    fp = WriteBuffer(open(output_module_files[m], 'w'))
173    m = Module(settings, m, functions_for_module[m], calls_for_module[m],
174               functions)
175    m.emit(fp)
176    fp.close()
177
178  print("callingconv seed: %d" % settings.seed)
179  fp = open(output_golden_file, 'w')
180  fp.write("generate.py arguments: %s\n" % settings._script_argv)
181  fp.write("SUCCESS: %d calls OK.\n" % num_asserts)
182  fp.close()
183
184  return 0
185
186def Fatal(m, *args):
187  if len(args) > 0:
188    m = m % args
189  print(m)
190  sys.exit(2)
191
192class WriteBuffer(object):
193  def __init__(self, fp):
194    self.fp = fp
195    self.buffer = ''
196
197  def write(self, data):
198    self.buffer += data
199
200  def flush(self):
201    self.fp.write(self.buffer)
202    self.buffer = ''
203
204  def close(self):
205    self.flush()
206    self.fp.close()
207
208
209def randsplit(inlist, n):
210  """ Randomly split a list into n sublists. """
211  inlist = list(inlist)
212  random.shuffle(inlist)
213  selections = [ (random.randint(0, n-1), m) for m in inlist ]
214  sublists = [ [] for i in xrange(n) ]
215  for (i, m) in selections:
216    sublists[i].append(m)
217  return sublists
218
219
220class Module(object):
221  def __init__(self, settings, id, functions, calls, all_functions):
222    self.settings = settings
223    self.id = id
224    self.calls = calls
225    self.functions = functions
226    self.all_functions = all_functions
227
228  def emit(self, out):
229    out.write('\n'.join(LICENSE))
230    out.write('\n')
231
232    out.write("/*--------------------------------------------------*\n"
233              " *             THIS FILE IS AUTO-GENERATED          *\n"
234              " *              DO NOT MODIFY THIS FILE             *\n"
235              " *            MODIFY  'generate.py'  instead        *\n"
236              " *--------------------------------------------------*/\n")
237    out.write("\n")
238
239    # only emit this for the first module we generate
240    if self.id == 0:
241      out.write("const char *script_argv =\n")
242      out.write("  \"" + CEscape(self.settings._script_argv) + "\";\n")
243
244    out.write("/*--------------------------------------------------*\n"
245              " * Generator Settings:                              *\n")
246    for k,v in self.settings.__dict__.iteritems():
247      if k.startswith('_'):
248        continue
249      lines = WordWrap(str(v), 22)
250      for i, s in enumerate(lines):
251        if i == 0:
252          out.write(" *  %-21s = %-22s  *\n" % (k, s))
253        else:
254          out.write(" *  %-21s    %-22s *\n" % ('', s))
255    out.write(" *--------------------------------------------------*/\n")
256    out.write("\n")
257
258    out.write('#include <stdio.h>\n')
259    out.write('#include <stdlib.h>\n')
260    out.write('#include <string.h>\n')
261    out.write('#include <stdarg.h>\n')
262    out.write('#include <callingconv.h>\n')
263    out.write("\n")
264
265    # Emit function prototypes (all of them)
266    for f in self.all_functions:
267      f.emit_prototype(out)
268    out.write("\n")
269
270    # Emit prototypes of the vcheck functions
271    for module_id in xrange(self.settings.num_modules):
272      out.write("void vcheck%d(va_list ap, int i, char type);\n" % module_id)
273    out.write("\n")
274
275    # Emit the main module function
276    out.write("void module%d(void) {\n" % self.id)
277    out.write("  SET_CURRENT_MODULE(%d);\n" % self.id)
278    out.write("\n")
279    for c in self.calls:
280      c.emit(out)
281    out.write("}\n\n")
282
283    # Emit the "vcheck" function, for checking
284    # va_list passing.
285    out.write("void vcheck%d(va_list ap, int i, char type) {\n" % self.id)
286    for t in self.settings.va_arg_types:
287      va_arg_val = "va_arg(ap, %s)" % t.id
288      expected_val = "v_%s[i]" % t.id
289      comparison = t.compare_expr % (va_arg_val, expected_val)
290      out.write("  if (type == '%s')\n" % t.format)
291      out.write("    ASSERT(%s);\n" % comparison)
292      out.write("")
293    out.write("}\n\n")
294
295    # Emit the function definitions in this module
296    for f in self.functions:
297      f.emit(out)
298    out.write("\n")
299
300def CEscape(s):
301  s = s.replace('\\', '\\\\')
302  s = s.replace('"', '\\"')
303  return s
304
305def WordWrap(s, linelen):
306  words = s.split(' ')
307  lines = []
308  line = ''
309  spaces = 0
310  for w in words:
311    if len(line) + spaces + len(w) > linelen:
312      lines.append(line)
313      line = ''
314      spaces = 0
315    line += (' ' * spaces) + w
316    spaces = 1
317  lines.append(line)
318  return lines
319
320def GenerateTest(settings):
321  random.seed(settings.seed)
322
323  functions = [ TestFunction(settings, i)
324                for i in xrange(settings.num_functions) ]
325
326  calls = [ ]
327  callcount = 0
328  for f in functions:
329    for i in xrange(settings.calls_per_func):
330      calls.append(TestCall(settings, callcount, f))
331      callcount += 1
332
333  num_asserts = sum([ c.num_asserts for c in calls ])
334  return (functions, calls, num_asserts)
335
336
337class TestCall(object):
338  def __init__(self, settings, call_id, function):
339    self.settings = settings
340    self.id = call_id
341    self.f = function
342
343    self.num_var_args = random.randint(0, self.settings.max_args_per_func -
344                                          self.f.num_fixed_args)
345
346    # Generate argument inputs
347    self.num_args = self.f.num_fixed_args + self.num_var_args
348    args = []
349    fmtstr = ''
350    for i in xrange(self.num_args):
351      if i < self.f.num_fixed_args:
352        t = self.f.fixed_arg_types[i]
353      else:
354        t = random.choice(settings.va_arg_types)
355      if i == self.f.num_fixed_args:
356        fmtstr += '" "'
357      fmtstr += str(t.format)
358      args.append((i, t, t.maker()))
359    self.args = args
360    self.fmtstr = fmtstr
361
362    # Each fixed argument is checked once.
363    # Each variable argument is checked once by the test function,
364    # and once by each vcheck() call. (one per module)
365    self.num_asserts = (self.f.num_fixed_args +
366                        self.num_var_args * (settings.num_modules + 1))
367
368  def emit(self, out):
369    out.write("  /* C%d */\n" % self.id)
370    out.write("  SET_CURRENT_CALL(%d);\n" % self.id)
371
372    # Set the check variables
373    for (i, t, value) in self.args:
374      var = "v_%s[%d]" % (t.id, i)
375      setexpr = t.assign_stmt % (var, value)
376      out.write(prettycode('  ' + setexpr) + '\n')
377
378    # Call the function
379    argstr = ''
380    for (i, t, value) in self.args:
381      argstr += ', v_%s[%d]' % (t.id, i)
382    callstr = '  F%s("%s"%s);\n' % (self.f.id, self.fmtstr, argstr)
383    out.write(prettycode(callstr))
384    out.write("\n")
385
386
387def prettycode(str):
388  """Try to beautify a line of code."""
389
390  if len(str) < 80:
391    return str
392
393  lparen = str.find('(')
394  rparen = str.rfind(')')
395  if lparen < 0 or rparen < 0:
396    Fatal("Invalid code string")
397  head = str[ 0 : lparen ]
398  inner = str[ lparen+1 : rparen ]
399  tail = str[ rparen+1 : ]
400  args = inner.split(",")
401  ret = head + "("
402  pos = len(ret)
403  indent = len(ret)
404  firstarg = True
405  for arg in args:
406    if firstarg:
407      s = arg.strip()
408      firstarg = False
409    elif pos + len(arg) < 75:
410      s = ", " + arg.strip()
411    else:
412      ret += ",\n" + (' ' * indent)
413      pos = indent
414      s = arg.strip()
415    ret += s
416    pos += len(s)
417  ret += ")" + tail
418  return ret
419
420
421
422# Represents a randomly generated test function
423class TestFunction(object):
424  def __init__(self, settings, id):
425    self.settings = settings
426    self.id = id
427    self.num_fixed_args = random.randint(0, self.settings.max_args_per_func)
428    self.fixed_arg_types = range(self.num_fixed_args)
429    for i in xrange(len(self.fixed_arg_types)):
430      self.fixed_arg_types[i] = random.choice(settings.all_types)
431
432
433  def emit_prototype(self, out, is_def = False):
434    fixed_arg_str = ''
435    argnum = 0
436    for t in self.fixed_arg_types:
437      fixed_arg_str += ", %s a_%d" % (t.id, argnum)
438      argnum += 1
439
440    prototype = "void F%s(const char *fmt%s, ...)" % (self.id, fixed_arg_str)
441    out.write(prettycode(prototype))
442
443    if is_def:
444      out.write(" {\n")
445    else:
446      out.write(";\n")
447      out.write("\n")
448
449  def emit(self, out):
450    self.emit_prototype(out, True)
451
452    out.write("  va_list ap;\n")
453    out.write("  va_list ap2;\n")
454    out.write("  int i = 0;\n")
455    out.write("\n")
456
457    out.write("  SET_CURRENT_FUNCTION(%d);\n" % self.id)
458    out.write("  SET_INDEX_VARIABLE(i);\n")
459    out.write("\n")
460
461    if self.num_fixed_args > 0:
462      out.write("  /* Handle fixed arguments */\n")
463      for (i, t) in enumerate(self.fixed_arg_types):
464        arg_val   = "a_%d" % i
465        check_val = "v_%s[i]" % (t.id)
466        comp_expr = t.compare_expr % (arg_val, check_val)
467        out.write("  ASSERT(%s); i++;\n" % (comp_expr,))
468      out.write("\n")
469      # The last fixed argument
470      last_arg = arg_val
471    else:
472      last_arg = "fmt"
473
474    # Handle variable arguments
475    out.write("  /* Handle variable arguments */\n")
476    out.write("  va_start(ap, %s);\n" % last_arg)
477    out.write("  while (fmt[i]) {\n")
478    out.write("    switch (fmt[i]) {\n")
479
480    for t in self.settings.va_arg_types:
481      arg_val = "va_arg(ap, %s)" % t.id
482      check_val = "v_%s[i]" % t.id
483      comp_expr = t.compare_expr % (arg_val, check_val)
484      out.write("    case '%s':\n" % t.format)
485      for module_id in xrange(self.settings.num_modules):
486        out.write("      va_copy(ap2, ap);\n")
487        out.write("      vcheck%d(ap2, i, fmt[i]);\n" % module_id)
488        out.write("      va_end(ap2);\n")
489      out.write("      ASSERT(%s);\n" % comp_expr)
490      out.write("      break;\n")
491    out.write("    default:\n")
492    out.write('      printf("Unexpected type code\\n");\n')
493    out.write("      exit(1);\n")
494    out.write("    }\n")
495    out.write("    i++;\n")
496    out.write("  }\n")
497    out.write("  va_end(ap);\n")
498    out.write("}\n\n")
499
500alphabet = string.letters + string.digits
501
502def make_string():
503  global alphabet
504  randstr = [random.choice(alphabet) for i in xrange(random.randint(0,16))]
505  randstr = ''.join(randstr)
506  return '"%s"' % randstr
507
508def make_int():
509  B = pow(2,31)
510  return str(random.randint(-B, B-1))
511
512def make_long():
513  B = pow(2,31)
514  return str(random.randint(-B, B-1)) + "L"
515
516def make_llong():
517  B = pow(2,63)
518  return str(random.randint(-B, B-1)) + "LL"
519
520def make_float():
521  return str(random.random() * pow(2.0, random.randint(-126, 127)))
522
523def make_double():
524  return str( random.random() * pow(2.0, random.randint(-1022, 1023)) )
525
526def make_ldouble():
527  return "((long double)%s)" % make_double()
528
529def make_char():
530  global alphabet
531  return "'%s'" % random.choice(alphabet)
532
533def make_short():
534  B = pow(2,15)
535  return str(random.randint(-B, B-1))
536
537def make_tiny():
538  a = make_char()
539  b = make_short()
540  return "%s,%s" % (a,b)
541
542def make_big():
543  a = make_char()
544  b = make_char()
545  c = make_int()
546  d = make_char()
547  e = make_int()
548  f = make_char()
549  g = make_llong()
550  h = make_char()
551  i = make_int()
552  j = make_char()
553  k = make_short()
554  l = make_char()
555  m = make_double()
556  n = make_char()
557  return "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % \
558         (a,b,c,d,e,f,g,h,i,j,k,l,m,n)
559
560if __name__ == "__main__":
561  sys.exit(main(sys.argv[1:]))
562