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