| #!/usr/bin/env python3 | |
| # Copyright 2016 the V8 project authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| from collections import namedtuple | |
| import textwrap | |
| import sys | |
| SHARD_FILENAME_TEMPLATE = "test/mjsunit/compiler/inline-exception-{shard}.js" | |
| # Generates 2 files. Found by trial and error. | |
| SHARD_SIZE = 97 | |
| PREAMBLE = """ | |
| // Copyright 2016 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // Flags: --allow-natives-syntax --no-always-opt | |
| // This test file was generated by tools/gen-inlining-tests.py . | |
| // Global variables | |
| var deopt = undefined; // either true or false | |
| var counter = 0; | |
| function resetState() { | |
| counter = 0; | |
| } | |
| function warmUp(f) { | |
| try { | |
| f(); | |
| } catch (ex) { | |
| // ok | |
| } | |
| try { | |
| f(); | |
| } catch (ex) { | |
| // ok | |
| } | |
| } | |
| function resetOptAndAssertResultEquals(expected, f) { | |
| warmUp(f); | |
| resetState(); | |
| // %DebugPrint(f); | |
| eval("'dont optimize this function itself please, but do optimize f'"); | |
| %OptimizeFunctionOnNextCall(f); | |
| assertEquals(expected, f()); | |
| } | |
| function resetOptAndAssertThrowsWith(expected, f) { | |
| warmUp(f); | |
| resetState(); | |
| // %DebugPrint(f); | |
| eval("'dont optimize this function itself please, but do optimize f'"); | |
| %OptimizeFunctionOnNextCall(f); | |
| try { | |
| var result = f(); | |
| fail("resetOptAndAssertThrowsWith", | |
| "exception: " + expected, | |
| "result: " + result); | |
| } catch (ex) { | |
| assertEquals(expected, ex); | |
| } | |
| } | |
| function increaseAndReturn15() { | |
| if (deopt) %DeoptimizeFunction(f); | |
| counter++; | |
| return 15; | |
| } | |
| function increaseAndThrow42() { | |
| if (deopt) %DeoptimizeFunction(f); | |
| counter++; | |
| throw 42; | |
| } | |
| function increaseAndReturn15_noopt_inner() { | |
| if (deopt) %DeoptimizeFunction(f); | |
| counter++; | |
| return 15; | |
| } | |
| %NeverOptimizeFunction(increaseAndReturn15_noopt_inner); | |
| function increaseAndThrow42_noopt_inner() { | |
| if (deopt) %DeoptimizeFunction(f); | |
| counter++; | |
| throw 42; | |
| } | |
| %NeverOptimizeFunction(increaseAndThrow42_noopt_inner); | |
| // Alternative 1 | |
| function returnOrThrow(doReturn) { | |
| if (doReturn) { | |
| return increaseAndReturn15(); | |
| } else { | |
| return increaseAndThrow42(); | |
| } | |
| } | |
| // Alternative 2 | |
| function increaseAndReturn15_calls_noopt() { | |
| return increaseAndReturn15_noopt_inner(); | |
| } | |
| function increaseAndThrow42_calls_noopt() { | |
| return increaseAndThrow42_noopt_inner(); | |
| } | |
| // Alternative 3. | |
| // When passed either {increaseAndReturn15} or {increaseAndThrow42}, it acts | |
| // as the other one. | |
| function invertFunctionCall(f) { | |
| var result; | |
| try { | |
| result = f(); | |
| } catch (ex) { | |
| return ex - 27; | |
| } | |
| throw result + 27; | |
| } | |
| // Alternative 4: constructor | |
| function increaseAndStore15Constructor() { | |
| if (deopt) %DeoptimizeFunction(f); | |
| ++counter; | |
| this.x = 15; | |
| } | |
| function increaseAndThrow42Constructor() { | |
| if (deopt) %DeoptimizeFunction(f); | |
| ++counter; | |
| this.x = 42; | |
| throw this.x; | |
| } | |
| // Alternative 5: property | |
| var magic = {}; | |
| Object.defineProperty(magic, 'prop', { | |
| get: function () { | |
| if (deopt) %DeoptimizeFunction(f); | |
| return 15 + 0 * ++counter; | |
| }, | |
| set: function(x) { | |
| // argument should be 37 | |
| if (deopt) %DeoptimizeFunction(f); | |
| counter -= 36 - x; // increments counter | |
| throw 42; | |
| } | |
| }) | |
| // Generate type feedback. | |
| assertEquals(15, increaseAndReturn15_calls_noopt()); | |
| assertThrowsEquals(function() { return increaseAndThrow42_noopt_inner() }, 42); | |
| assertEquals(15, (new increaseAndStore15Constructor()).x); | |
| assertThrowsEquals(function() { | |
| return (new increaseAndThrow42Constructor()).x; | |
| }, | |
| 42); | |
| function runThisShard() { | |
| """.strip() | |
| def booltuples(n): | |
| """booltuples(2) yields 4 tuples: (False, False), (False, True), | |
| (True, False), (True, True).""" | |
| assert isinstance(n, int) | |
| if n <= 0: | |
| yield () | |
| else: | |
| for initial in booltuples(n-1): | |
| yield initial + (False,) | |
| yield initial + (True,) | |
| def fnname(flags): | |
| assert len(FLAGLETTERS) == len(flags) | |
| return "f_" + ''.join( | |
| FLAGLETTERS[i] if b else '_' | |
| for (i, b) in enumerate(flags)) | |
| NUM_TESTS_PRINTED = 0 | |
| NUM_TESTS_IN_SHARD = 0 | |
| def printtest(flags): | |
| """Print a test case. Takes a couple of boolean flags, on which the | |
| printed Javascript code depends.""" | |
| assert all(isinstance(flag, bool) for flag in flags) | |
| # The alternative flags are in reverse order so that if we take all possible | |
| # tuples, ordered lexicographically from false to true, we get first the | |
| # default, then alternative 1, then 2, etc. | |
| ( | |
| alternativeFn5, # use alternative #5 for returning/throwing: | |
| # return/throw using property | |
| alternativeFn4, # use alternative #4 for returning/throwing: | |
| # return/throw using constructor | |
| alternativeFn3, # use alternative #3 for returning/throwing: | |
| # return/throw indirectly, based on function argument | |
| alternativeFn2, # use alternative #2 for returning/throwing: | |
| # return/throw indirectly in unoptimized code, | |
| # no branching | |
| alternativeFn1, # use alternative #1 for returning/throwing: | |
| # return/throw indirectly, based on boolean arg | |
| tryThrows, # in try block, call throwing function | |
| tryReturns, # in try block, call returning function | |
| tryFirstReturns, # in try block, returning goes before throwing | |
| tryResultToLocal, # in try block, result goes to local variable | |
| doCatch, # include catch block | |
| catchReturns, # in catch block, return | |
| catchWithLocal, # in catch block, modify or return the local variable | |
| catchThrows, # in catch block, throw | |
| doFinally, # include finally block | |
| finallyReturns, # in finally block, return local variable | |
| finallyThrows, # in finally block, throw | |
| endReturnLocal, # at very end, return variable local | |
| deopt, # deopt inside inlined function | |
| ) = flags | |
| # BASIC RULES | |
| # Only one alternative can be applied at any time. | |
| if (alternativeFn1 + alternativeFn2 + alternativeFn3 + alternativeFn4 | |
| + alternativeFn5 > 1): | |
| return | |
| # In try, return or throw, or both. | |
| if not (tryReturns or tryThrows): return | |
| # Either doCatch or doFinally. | |
| if not doCatch and not doFinally: return | |
| # Catch flags only make sense when catching | |
| if not doCatch and (catchReturns or catchWithLocal or catchThrows): | |
| return | |
| # Finally flags only make sense when finallying | |
| if not doFinally and (finallyReturns or finallyThrows): | |
| return | |
| # tryFirstReturns is only relevant when both tryReturns and tryThrows are | |
| # true. | |
| if tryFirstReturns and not (tryReturns and tryThrows): return | |
| # From the try and finally block, we can return or throw, but not both. | |
| if catchReturns and catchThrows: return | |
| if finallyReturns and finallyThrows: return | |
| # If at the end we return the local, we need to have touched it. | |
| if endReturnLocal and not (tryResultToLocal or catchWithLocal): return | |
| # PRUNING | |
| anyAlternative = any([alternativeFn1, alternativeFn2, alternativeFn3, | |
| alternativeFn4, alternativeFn5]) | |
| specificAlternative = any([alternativeFn2, alternativeFn3]) | |
| rareAlternative = not specificAlternative | |
| # If try returns and throws, then don't catchWithLocal, endReturnLocal, or | |
| # deopt, or do any alternative. | |
| if (tryReturns and tryThrows and | |
| (catchWithLocal or endReturnLocal or deopt or anyAlternative)): | |
| return | |
| # We don't do any alternative if we do a finally. | |
| if doFinally and anyAlternative: return | |
| # We only use the local variable if we do alternative #2 or #3. | |
| if ((tryResultToLocal or catchWithLocal or endReturnLocal) and | |
| not specificAlternative): | |
| return | |
| # We don't need to test deopting into a finally. | |
| if doFinally and deopt: return | |
| # We're only interested in alternative #2 if we have endReturnLocal, no | |
| # catchReturns, and no catchThrows, and deopt. | |
| if (alternativeFn2 and | |
| (not endReturnLocal or catchReturns or catchThrows or not deopt)): | |
| return | |
| # Flag check succeeded. | |
| trueFlagNames = [name for (name, value) in flags._asdict().items() if value] | |
| flagsMsgLine = " // Variant flags: [{}]".format(', '.join(trueFlagNames)) | |
| write(textwrap.fill(flagsMsgLine, subsequent_indent=' // ')) | |
| write("") | |
| if not anyAlternative: | |
| fragments = { | |
| 'increaseAndReturn15': 'increaseAndReturn15()', | |
| 'increaseAndThrow42': 'increaseAndThrow42()', | |
| } | |
| elif alternativeFn1: | |
| fragments = { | |
| 'increaseAndReturn15': 'returnOrThrow(true)', | |
| 'increaseAndThrow42': 'returnOrThrow(false)', | |
| } | |
| elif alternativeFn2: | |
| fragments = { | |
| 'increaseAndReturn15': 'increaseAndReturn15_calls_noopt()', | |
| 'increaseAndThrow42': 'increaseAndThrow42_calls_noopt()', | |
| } | |
| elif alternativeFn3: | |
| fragments = { | |
| 'increaseAndReturn15': 'invertFunctionCall(increaseAndThrow42)', | |
| 'increaseAndThrow42': 'invertFunctionCall(increaseAndReturn15)', | |
| } | |
| elif alternativeFn4: | |
| fragments = { | |
| 'increaseAndReturn15': '(new increaseAndStore15Constructor()).x', | |
| 'increaseAndThrow42': '(new increaseAndThrow42Constructor()).x', | |
| } | |
| else: | |
| assert alternativeFn5 | |
| fragments = { | |
| 'increaseAndReturn15': 'magic.prop /* returns 15 */', | |
| 'increaseAndThrow42': '(magic.prop = 37 /* throws 42 */)', | |
| } | |
| # As we print code, we also maintain what the result should be. Variable | |
| # {result} can be one of three things: | |
| # | |
| # - None, indicating returning JS null | |
| # - ("return", n) with n an integer | |
| # - ("throw", n), with n an integer | |
| result = None | |
| # We also maintain what the counter should be at the end. | |
| # The counter is reset just before f is called. | |
| counter = 0 | |
| write( " f = function {} () {{".format(fnname(flags))) | |
| write( " var local = 888;") | |
| write( " deopt = {};".format("true" if deopt else "false")) | |
| local = 888 | |
| write( " try {") | |
| write( " counter++;") | |
| counter += 1 | |
| resultTo = "local +=" if tryResultToLocal else "return" | |
| if tryReturns and not (tryThrows and not tryFirstReturns): | |
| write( " {} 4 + {increaseAndReturn15};".format(resultTo, **fragments)) | |
| if result == None: | |
| counter += 1 | |
| if tryResultToLocal: | |
| local += 19 | |
| else: | |
| result = ("return", 19) | |
| if tryThrows: | |
| write( " {} 4 + {increaseAndThrow42};".format(resultTo, **fragments)) | |
| if result == None: | |
| counter += 1 | |
| result = ("throw", 42) | |
| if tryReturns and tryThrows and not tryFirstReturns: | |
| write( " {} 4 + {increaseAndReturn15};".format(resultTo, **fragments)) | |
| if result == None: | |
| counter += 1 | |
| if tryResultToLocal: | |
| local += 19 | |
| else: | |
| result = ("return", 19) | |
| write( " counter++;") | |
| if result == None: | |
| counter += 1 | |
| if doCatch: | |
| write( " } catch (ex) {") | |
| write( " counter++;") | |
| if isinstance(result, tuple) and result[0] == 'throw': | |
| counter += 1 | |
| if catchThrows: | |
| write(" throw 2 + ex;") | |
| if isinstance(result, tuple) and result[0] == "throw": | |
| result = ('throw', 2 + result[1]) | |
| elif catchReturns and catchWithLocal: | |
| write(" return 2 + local;") | |
| if isinstance(result, tuple) and result[0] == "throw": | |
| result = ('return', 2 + local) | |
| elif catchReturns and not catchWithLocal: | |
| write(" return 2 + ex;"); | |
| if isinstance(result, tuple) and result[0] == "throw": | |
| result = ('return', 2 + result[1]) | |
| elif catchWithLocal: | |
| write(" local += ex;"); | |
| if isinstance(result, tuple) and result[0] == "throw": | |
| local += result[1] | |
| result = None | |
| counter += 1 | |
| else: | |
| if isinstance(result, tuple) and result[0] == "throw": | |
| result = None | |
| counter += 1 | |
| write( " counter++;") | |
| if doFinally: | |
| write( " } finally {") | |
| write( " counter++;") | |
| counter += 1 | |
| if finallyThrows: | |
| write(" throw 25;") | |
| result = ('throw', 25) | |
| elif finallyReturns: | |
| write(" return 3 + local;") | |
| result = ('return', 3 + local) | |
| elif not finallyReturns and not finallyThrows: | |
| write(" local += 2;") | |
| local += 2 | |
| counter += 1 | |
| else: assert False # unreachable | |
| write( " counter++;") | |
| write( " }") | |
| write( " counter++;") | |
| if result == None: | |
| counter += 1 | |
| if endReturnLocal: | |
| write( " return 5 + local;") | |
| if result == None: | |
| result = ('return', 5 + local) | |
| write( " }") | |
| if result == None: | |
| write( " resetOptAndAssertResultEquals(undefined, f);") | |
| else: | |
| tag, value = result | |
| if tag == "return": | |
| write( " resetOptAndAssertResultEquals({}, f);".format(value)) | |
| else: | |
| assert tag == "throw" | |
| write( " resetOptAndAssertThrowsWith({}, f);".format(value)) | |
| write( " assertEquals({}, counter);".format(counter)) | |
| write( "") | |
| global NUM_TESTS_PRINTED, NUM_TESTS_IN_SHARD | |
| NUM_TESTS_PRINTED += 1 | |
| NUM_TESTS_IN_SHARD += 1 | |
| FILE = None # to be initialised to an open file | |
| SHARD_NUM = 1 | |
| def write(*args): | |
| return print(*args, file=FILE) | |
| def rotateshard(): | |
| global FILE, NUM_TESTS_IN_SHARD, SHARD_SIZE | |
| if MODE != 'shard': | |
| return | |
| if FILE != None and NUM_TESTS_IN_SHARD < SHARD_SIZE: | |
| return | |
| if FILE != None: | |
| finishshard() | |
| assert FILE == None | |
| FILE = open(SHARD_FILENAME_TEMPLATE.format(shard=SHARD_NUM), 'w') | |
| write_shard_header() | |
| NUM_TESTS_IN_SHARD = 0 | |
| def finishshard(): | |
| global FILE, SHARD_NUM, MODE | |
| assert FILE | |
| write_shard_footer() | |
| if MODE == 'shard': | |
| print("Wrote shard {}.".format(SHARD_NUM)) | |
| FILE.close() | |
| FILE = None | |
| SHARD_NUM += 1 | |
| def write_shard_header(): | |
| if MODE == 'shard': | |
| write("// Shard {}.".format(SHARD_NUM)) | |
| write("") | |
| write(PREAMBLE) | |
| write("") | |
| def write_shard_footer(): | |
| write("}") | |
| write("%NeverOptimizeFunction(runThisShard);") | |
| write("") | |
| write("// {} tests in this shard.".format(NUM_TESTS_IN_SHARD)) | |
| write("// {} tests up to here.".format(NUM_TESTS_PRINTED)) | |
| write("") | |
| write("runThisShard();") | |
| FLAGLETTERS="54321trflcrltfrtld" | |
| flagtuple = namedtuple('flagtuple', ( | |
| "alternativeFn5", | |
| "alternativeFn4", | |
| "alternativeFn3", | |
| "alternativeFn2", | |
| "alternativeFn1", | |
| "tryThrows", | |
| "tryReturns", | |
| "tryFirstReturns", | |
| "tryResultToLocal", | |
| "doCatch", | |
| "catchReturns", | |
| "catchWithLocal", | |
| "catchThrows", | |
| "doFinally", | |
| "finallyReturns", | |
| "finallyThrows", | |
| "endReturnLocal", | |
| "deopt" | |
| )) | |
| emptyflags = flagtuple(*((False,) * len(flagtuple._fields))) | |
| f1 = emptyflags._replace(tryReturns=True, doCatch=True) | |
| # You can test function printtest with f1. | |
| allFlagCombinations = [ | |
| flagtuple(*bools) | |
| for bools in booltuples(len(flagtuple._fields)) | |
| ] | |
| if __name__ == '__main__': | |
| global MODE | |
| if sys.argv[1:] == []: | |
| MODE = 'stdout' | |
| print("// Printing all shards together to stdout.") | |
| print("") | |
| write_shard_header() | |
| FILE = sys.stdout | |
| elif sys.argv[1:] == ['--shard-and-overwrite']: | |
| MODE = 'shard' | |
| else: | |
| print("Usage:") | |
| print("") | |
| print(" python {}".format(sys.argv[0])) | |
| print(" print all tests to standard output") | |
| print(" python {} --shard-and-overwrite".format(sys.argv[0])) | |
| print(" print all tests to {}".format(SHARD_FILENAME_TEMPLATE)) | |
| print("") | |
| print(sys.argv[1:]) | |
| print("") | |
| sys.exit(1) | |
| rotateshard() | |
| for flags in allFlagCombinations: | |
| printtest(flags) | |
| rotateshard() | |
| finishshard() | |
| if MODE == 'shard': | |
| print("Total: {} tests.".format(NUM_TESTS_PRINTED)) |