Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python3
      2 
      3 # Copyright 2016 the V8 project authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 
      8 from collections import namedtuple
      9 import textwrap
     10 import sys
     11 
     12 SHARD_FILENAME_TEMPLATE = "test/mjsunit/compiler/inline-exception-{shard}.js"
     13 # Generates 2 files. Found by trial and error.
     14 SHARD_SIZE = 97
     15 
     16 PREAMBLE = """
     17 
     18 // Copyright 2016 the V8 project authors. All rights reserved.
     19 // Use of this source code is governed by a BSD-style license that can be
     20 // found in the LICENSE file.
     21 
     22 // Flags: --allow-natives-syntax --turbo --no-always-opt
     23 
     24 // This test file was generated by tools/gen-inlining-tests.py .
     25 
     26 // Global variables
     27 var deopt = undefined; // either true or false
     28 var counter = 0;
     29 
     30 function resetState() {
     31   counter = 0;
     32 }
     33 
     34 function warmUp(f) {
     35   try {
     36     f();
     37   } catch (ex) {
     38     // ok
     39   }
     40   try {
     41     f();
     42   } catch (ex) {
     43     // ok
     44   }
     45 }
     46 
     47 function resetOptAndAssertResultEquals(expected, f) {
     48   warmUp(f);
     49   resetState();
     50   // %DebugPrint(f);
     51   eval("'dont optimize this function itself please, but do optimize f'");
     52   %OptimizeFunctionOnNextCall(f);
     53   assertEquals(expected, f());
     54 }
     55 
     56 function resetOptAndAssertThrowsWith(expected, f) {
     57   warmUp(f);
     58   resetState();
     59   // %DebugPrint(f);
     60   eval("'dont optimize this function itself please, but do optimize f'");
     61   %OptimizeFunctionOnNextCall(f);
     62   try {
     63     var result = f();
     64     fail("resetOptAndAssertThrowsWith",
     65         "exception: " + expected,
     66         "result: " + result);
     67   } catch (ex) {
     68     assertEquals(expected, ex);
     69   }
     70 }
     71 
     72 function increaseAndReturn15() {
     73   if (deopt) %DeoptimizeFunction(f);
     74   counter++;
     75   return 15;
     76 }
     77 
     78 function increaseAndThrow42() {
     79   if (deopt) %DeoptimizeFunction(f);
     80   counter++;
     81   throw 42;
     82 }
     83 
     84 function increaseAndReturn15_noopt_inner() {
     85   if (deopt) %DeoptimizeFunction(f);
     86   counter++;
     87   return 15;
     88 }
     89 
     90 %NeverOptimizeFunction(increaseAndReturn15_noopt_inner);
     91 
     92 function increaseAndThrow42_noopt_inner() {
     93   if (deopt) %DeoptimizeFunction(f);
     94   counter++;
     95   throw 42;
     96 }
     97 
     98 %NeverOptimizeFunction(increaseAndThrow42_noopt_inner);
     99 
    100 // Alternative 1
    101 
    102 function returnOrThrow(doReturn) {
    103   if (doReturn) {
    104     return increaseAndReturn15();
    105   } else {
    106     return increaseAndThrow42();
    107   }
    108 }
    109 
    110 // Alternative 2
    111 
    112 function increaseAndReturn15_calls_noopt() {
    113   return increaseAndReturn15_noopt_inner();
    114 }
    115 
    116 function increaseAndThrow42_calls_noopt() {
    117   return increaseAndThrow42_noopt_inner();
    118 }
    119 
    120 // Alternative 3.
    121 // When passed either {increaseAndReturn15} or {increaseAndThrow42}, it acts
    122 // as the other one.
    123 function invertFunctionCall(f) {
    124   var result;
    125   try {
    126     result = f();
    127   } catch (ex) {
    128     return ex - 27;
    129   }
    130   throw result + 27;
    131 }
    132 
    133 // Alternative 4: constructor
    134 function increaseAndStore15Constructor() {
    135   if (deopt) %DeoptimizeFunction(f);
    136   ++counter;
    137   this.x = 15;
    138 }
    139 
    140 function increaseAndThrow42Constructor() {
    141   if (deopt) %DeoptimizeFunction(f);
    142   ++counter;
    143   this.x = 42;
    144   throw this.x;
    145 }
    146 
    147 // Alternative 5: property
    148 var magic = {};
    149 Object.defineProperty(magic, 'prop', {
    150   get: function () {
    151     if (deopt) %DeoptimizeFunction(f);
    152     return 15 + 0 * ++counter;
    153   },
    154 
    155   set: function(x) {
    156     // argument should be 37
    157     if (deopt) %DeoptimizeFunction(f);
    158     counter -= 36 - x; // increments counter
    159     throw 42;
    160   }
    161 })
    162 
    163 // Generate type feedback.
    164 
    165 assertEquals(15, increaseAndReturn15_calls_noopt());
    166 assertThrowsEquals(function() { return increaseAndThrow42_noopt_inner() }, 42);
    167 
    168 assertEquals(15, (new increaseAndStore15Constructor()).x);
    169 assertThrowsEquals(function() {
    170         return (new increaseAndThrow42Constructor()).x;
    171     },
    172     42);
    173 
    174 function runThisShard() {
    175 
    176 """.strip()
    177 
    178 def booltuples(n):
    179   """booltuples(2) yields 4 tuples: (False, False), (False, True),
    180   (True, False), (True, True)."""
    181 
    182   assert isinstance(n, int)
    183   if n <= 0:
    184     yield ()
    185   else:
    186     for initial in booltuples(n-1):
    187       yield initial + (False,)
    188       yield initial + (True,)
    189 
    190 def fnname(flags):
    191     assert len(FLAGLETTERS) == len(flags)
    192 
    193     return "f_" + ''.join(
    194           FLAGLETTERS[i] if b else '_'
    195           for (i, b) in enumerate(flags))
    196 
    197 NUM_TESTS_PRINTED = 0
    198 NUM_TESTS_IN_SHARD = 0
    199 
    200 def printtest(flags):
    201   """Print a test case. Takes a couple of boolean flags, on which the
    202   printed Javascript code depends."""
    203 
    204   assert all(isinstance(flag, bool) for flag in flags)
    205 
    206   # The alternative flags are in reverse order so that if we take all possible
    207   # tuples, ordered lexicographically from false to true, we get first the
    208   # default, then alternative 1, then 2, etc.
    209   (
    210     alternativeFn5,      # use alternative #5 for returning/throwing:
    211                          #   return/throw using property
    212     alternativeFn4,      # use alternative #4 for returning/throwing:
    213                          #   return/throw using constructor
    214     alternativeFn3,      # use alternative #3 for returning/throwing:
    215                          #   return/throw indirectly, based on function argument
    216     alternativeFn2,      # use alternative #2 for returning/throwing:
    217                          #   return/throw indirectly in unoptimized code,
    218                          #   no branching
    219     alternativeFn1,      # use alternative #1 for returning/throwing:
    220                          #   return/throw indirectly, based on boolean arg
    221     tryThrows,           # in try block, call throwing function
    222     tryReturns,          # in try block, call returning function
    223     tryFirstReturns,     # in try block, returning goes before throwing
    224     tryResultToLocal,    # in try block, result goes to local variable
    225     doCatch,             # include catch block
    226     catchReturns,        # in catch block, return
    227     catchWithLocal,      # in catch block, modify or return the local variable
    228     catchThrows,         # in catch block, throw
    229     doFinally,           # include finally block
    230     finallyReturns,      # in finally block, return local variable
    231     finallyThrows,       # in finally block, throw
    232     endReturnLocal,      # at very end, return variable local
    233     deopt,               # deopt inside inlined function
    234   ) = flags
    235 
    236   # BASIC RULES
    237 
    238   # Only one alternative can be applied at any time.
    239   if (alternativeFn1 + alternativeFn2 + alternativeFn3 + alternativeFn4
    240       + alternativeFn5 > 1):
    241     return
    242 
    243   # In try, return or throw, or both.
    244   if not (tryReturns or tryThrows): return
    245 
    246   # Either doCatch or doFinally.
    247   if not doCatch and not doFinally: return
    248 
    249   # Catch flags only make sense when catching
    250   if not doCatch and (catchReturns or catchWithLocal or catchThrows):
    251     return
    252 
    253   # Finally flags only make sense when finallying
    254   if not doFinally and (finallyReturns or finallyThrows):
    255     return
    256 
    257   # tryFirstReturns is only relevant when both tryReturns and tryThrows are
    258   # true.
    259   if tryFirstReturns and not (tryReturns and tryThrows): return
    260 
    261   # From the try and finally block, we can return or throw, but not both.
    262   if catchReturns and catchThrows: return
    263   if finallyReturns and finallyThrows: return
    264 
    265   # If at the end we return the local, we need to have touched it.
    266   if endReturnLocal and not (tryResultToLocal or catchWithLocal): return
    267 
    268   # PRUNING
    269 
    270   anyAlternative = any([alternativeFn1, alternativeFn2, alternativeFn3,
    271       alternativeFn4, alternativeFn5])
    272   specificAlternative = any([alternativeFn2, alternativeFn3])
    273   rareAlternative = not specificAlternative
    274 
    275   # If try returns and throws, then don't catchWithLocal, endReturnLocal, or
    276   # deopt, or do any alternative.
    277   if (tryReturns and tryThrows and
    278       (catchWithLocal or endReturnLocal or deopt or anyAlternative)):
    279     return
    280   # We don't do any alternative if we do a finally.
    281   if doFinally and anyAlternative: return
    282   # We only use the local variable if we do alternative #2 or #3.
    283   if ((tryResultToLocal or catchWithLocal or endReturnLocal) and
    284       not specificAlternative):
    285     return
    286   # We don't need to test deopting into a finally.
    287   if doFinally and deopt: return
    288 
    289   # We're only interested in alternative #2 if we have endReturnLocal, no
    290   # catchReturns, and no catchThrows, and deopt.
    291   if (alternativeFn2 and
    292       (not endReturnLocal or catchReturns or catchThrows or not deopt)):
    293     return
    294 
    295 
    296   # Flag check succeeded.
    297 
    298   trueFlagNames = [name for (name, value) in flags._asdict().items() if value]
    299   flagsMsgLine = "  // Variant flags: [{}]".format(', '.join(trueFlagNames))
    300   write(textwrap.fill(flagsMsgLine, subsequent_indent='  //   '))
    301   write("")
    302 
    303   if not anyAlternative:
    304     fragments = {
    305       'increaseAndReturn15': 'increaseAndReturn15()',
    306       'increaseAndThrow42': 'increaseAndThrow42()',
    307     }
    308   elif alternativeFn1:
    309     fragments = {
    310       'increaseAndReturn15': 'returnOrThrow(true)',
    311       'increaseAndThrow42': 'returnOrThrow(false)',
    312     }
    313   elif alternativeFn2:
    314     fragments = {
    315       'increaseAndReturn15': 'increaseAndReturn15_calls_noopt()',
    316       'increaseAndThrow42': 'increaseAndThrow42_calls_noopt()',
    317     }
    318   elif alternativeFn3:
    319     fragments = {
    320       'increaseAndReturn15': 'invertFunctionCall(increaseAndThrow42)',
    321       'increaseAndThrow42': 'invertFunctionCall(increaseAndReturn15)',
    322     }
    323   elif alternativeFn4:
    324     fragments = {
    325       'increaseAndReturn15': '(new increaseAndStore15Constructor()).x',
    326       'increaseAndThrow42': '(new increaseAndThrow42Constructor()).x',
    327     }
    328   else:
    329     assert alternativeFn5
    330     fragments = {
    331       'increaseAndReturn15': 'magic.prop /* returns 15 */',
    332       'increaseAndThrow42': '(magic.prop = 37 /* throws 42 */)',
    333     }
    334 
    335   # As we print code, we also maintain what the result should be. Variable
    336   # {result} can be one of three things:
    337   #
    338   # - None, indicating returning JS null
    339   # - ("return", n) with n an integer
    340   # - ("throw", n), with n an integer
    341 
    342   result = None
    343   # We also maintain what the counter should be at the end.
    344   # The counter is reset just before f is called.
    345   counter = 0
    346 
    347   write(    "  f = function {} () {{".format(fnname(flags)))
    348   write(    "    var local = 888;")
    349   write(    "    deopt = {};".format("true" if deopt else "false"))
    350   local = 888
    351   write(    "    try {")
    352   write(    "      counter++;")
    353   counter += 1
    354   resultTo = "local +=" if tryResultToLocal else "return"
    355   if tryReturns and not (tryThrows and not tryFirstReturns):
    356     write(  "      {} 4 + {increaseAndReturn15};".format(resultTo, **fragments))
    357     if result == None:
    358       counter += 1
    359       if tryResultToLocal:
    360         local += 19
    361       else:
    362         result = ("return", 19)
    363   if tryThrows:
    364     write(  "      {} 4 + {increaseAndThrow42};".format(resultTo, **fragments))
    365     if result == None:
    366       counter += 1
    367       result = ("throw", 42)
    368   if tryReturns and tryThrows and not tryFirstReturns:
    369     write(  "      {} 4 + {increaseAndReturn15};".format(resultTo, **fragments))
    370     if result == None:
    371       counter += 1
    372       if tryResultToLocal:
    373         local += 19
    374       else:
    375         result = ("return", 19)
    376   write(    "      counter++;")
    377   if result == None:
    378     counter += 1
    379 
    380   if doCatch:
    381     write(  "    } catch (ex) {")
    382     write(  "      counter++;")
    383     if isinstance(result, tuple) and result[0] == 'throw':
    384       counter += 1
    385     if catchThrows:
    386       write("      throw 2 + ex;")
    387       if isinstance(result, tuple) and result[0] == "throw":
    388         result = ('throw', 2 + result[1])
    389     elif catchReturns and catchWithLocal:
    390       write("      return 2 + local;")
    391       if isinstance(result, tuple) and result[0] == "throw":
    392         result = ('return', 2 + local)
    393     elif catchReturns and not catchWithLocal:
    394       write("      return 2 + ex;");
    395       if isinstance(result, tuple) and result[0] == "throw":
    396         result = ('return', 2 + result[1])
    397     elif catchWithLocal:
    398       write("      local += ex;");
    399       if isinstance(result, tuple) and result[0] == "throw":
    400         local += result[1]
    401         result = None
    402         counter += 1
    403     else:
    404       if isinstance(result, tuple) and result[0] == "throw":
    405         result = None
    406         counter += 1
    407     write(  "      counter++;")
    408 
    409   if doFinally:
    410     write(  "    } finally {")
    411     write(  "      counter++;")
    412     counter += 1
    413     if finallyThrows:
    414       write("      throw 25;")
    415       result = ('throw', 25)
    416     elif finallyReturns:
    417       write("      return 3 + local;")
    418       result = ('return', 3 + local)
    419     elif not finallyReturns and not finallyThrows:
    420       write("      local += 2;")
    421       local += 2
    422       counter += 1
    423     else: assert False # unreachable
    424     write(  "      counter++;")
    425 
    426   write(    "    }")
    427   write(    "    counter++;")
    428   if result == None:
    429     counter += 1
    430   if endReturnLocal:
    431     write(  "    return 5 + local;")
    432     if result == None:
    433       result = ('return', 5 + local)
    434   write(    "  }")
    435 
    436   if result == None:
    437     write(  "  resetOptAndAssertResultEquals(undefined, f);")
    438   else:
    439     tag, value = result
    440     if tag == "return":
    441       write(  "  resetOptAndAssertResultEquals({}, f);".format(value))
    442     else:
    443       assert tag == "throw"
    444       write(  "  resetOptAndAssertThrowsWith({}, f);".format(value))
    445 
    446   write(  "  assertEquals({}, counter);".format(counter))
    447   write(  "")
    448 
    449   global NUM_TESTS_PRINTED, NUM_TESTS_IN_SHARD
    450   NUM_TESTS_PRINTED += 1
    451   NUM_TESTS_IN_SHARD += 1
    452 
    453 FILE = None # to be initialised to an open file
    454 SHARD_NUM = 1
    455 
    456 def write(*args):
    457   return print(*args, file=FILE)
    458 
    459 
    460 
    461 def rotateshard():
    462   global FILE, NUM_TESTS_IN_SHARD, SHARD_SIZE
    463   if MODE != 'shard':
    464     return
    465   if FILE != None and NUM_TESTS_IN_SHARD < SHARD_SIZE:
    466     return
    467   if FILE != None:
    468     finishshard()
    469     assert FILE == None
    470   FILE = open(SHARD_FILENAME_TEMPLATE.format(shard=SHARD_NUM), 'w')
    471   write_shard_header()
    472   NUM_TESTS_IN_SHARD = 0
    473 
    474 def finishshard():
    475   global FILE, SHARD_NUM, MODE
    476   assert FILE
    477   write_shard_footer()
    478   if MODE == 'shard':
    479     print("Wrote shard {}.".format(SHARD_NUM))
    480     FILE.close()
    481     FILE = None
    482     SHARD_NUM += 1
    483 
    484 
    485 def write_shard_header():
    486   if MODE == 'shard':
    487     write("// Shard {}.".format(SHARD_NUM))
    488     write("")
    489   write(PREAMBLE)
    490   write("")
    491 
    492 def write_shard_footer():
    493   write("}")
    494   write("%NeverOptimizeFunction(runThisShard);")
    495   write("")
    496   write("// {} tests in this shard.".format(NUM_TESTS_IN_SHARD))
    497   write("// {} tests up to here.".format(NUM_TESTS_PRINTED))
    498   write("")
    499   write("runThisShard();")
    500 
    501 FLAGLETTERS="54321trflcrltfrtld"
    502 
    503 flagtuple = namedtuple('flagtuple', (
    504   "alternativeFn5",
    505   "alternativeFn4",
    506   "alternativeFn3",
    507   "alternativeFn2",
    508   "alternativeFn1",
    509   "tryThrows",
    510   "tryReturns",
    511   "tryFirstReturns",
    512   "tryResultToLocal",
    513   "doCatch",
    514   "catchReturns",
    515   "catchWithLocal",
    516   "catchThrows",
    517   "doFinally",
    518   "finallyReturns",
    519   "finallyThrows",
    520   "endReturnLocal",
    521   "deopt"
    522   ))
    523 
    524 emptyflags = flagtuple(*((False,) * len(flagtuple._fields)))
    525 f1 = emptyflags._replace(tryReturns=True, doCatch=True)
    526 
    527 # You can test function printtest with f1.
    528 
    529 allFlagCombinations = [
    530     flagtuple(*bools)
    531     for bools in booltuples(len(flagtuple._fields))
    532 ]
    533 
    534 if __name__ == '__main__':
    535   global MODE
    536   if sys.argv[1:] == []:
    537     MODE = 'stdout'
    538     print("// Printing all shards together to stdout.")
    539     print("")
    540     write_shard_header()
    541     FILE = sys.stdout
    542   elif sys.argv[1:] == ['--shard-and-overwrite']:
    543     MODE = 'shard'
    544   else:
    545     print("Usage:")
    546     print("")
    547     print("  python {}".format(sys.argv[0]))
    548     print("      print all tests to standard output")
    549     print("  python {} --shard-and-overwrite".format(sys.argv[0]))
    550     print("      print all tests to {}".format(SHARD_FILENAME_TEMPLATE))
    551 
    552     print("")
    553     print(sys.argv[1:])
    554     print("")
    555     sys.exit(1)
    556 
    557   rotateshard()
    558 
    559   for flags in allFlagCombinations:
    560     printtest(flags)
    561     rotateshard()
    562 
    563   finishshard()
    564 
    565   if MODE == 'shard':
    566     print("Total: {} tests.".format(NUM_TESTS_PRINTED))
    567