Chromium Code Reviews| Index: tools/gen-inlining-tests.py | 
| diff --git a/tools/gen-inlining-tests.py b/tools/gen-inlining-tests.py | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..6e1cdc66abf390d62cd0c55c80d3d683864ac89f | 
| --- /dev/null | 
| +++ b/tools/gen-inlining-tests.py | 
| @@ -0,0 +1,364 @@ | 
| +#!/usr/bin/env python3 | 
| + | 
| +# Copyright 2016 the V8 project authors. All rights reserved. | 
| 
 
Jarin
2016/08/10 12:02:52
Please replace with the short version of the copyr
 
bgeron
2016/08/10 14:37:27
Done.
 
 | 
| +# Redistribution and use in source and binary forms, with or without | 
| +# modification, are permitted provided that the following conditions are | 
| +# met: | 
| +# | 
| +# * Redistributions of source code must retain the above copyright | 
| +# notice, this list of conditions and the following disclaimer. | 
| +# * Redistributions in binary form must reproduce the above | 
| +# copyright notice, this list of conditions and the following | 
| +# disclaimer in the documentation and/or other materials provided | 
| +# with the distribution. | 
| +# * Neither the name of Google Inc. nor the names of its | 
| +# contributors may be used to endorse or promote products derived | 
| +# from this software without specific prior written permission. | 
| +# | 
| +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
| +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
| +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
| +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
| +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
| +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
| +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
| +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
| +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
| +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
| +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
| + | 
| + | 
| +from collections import namedtuple | 
| +import textwrap | 
| + | 
| +PREAMBLE = """ | 
| + | 
| +// Copyright 2016 the V8 project authors. All rights reserved. | 
| +// Redistribution and use in source and binary forms, with or without | 
| +// modification, are permitted provided that the following conditions are | 
| +// met: | 
| +// | 
| +// * Redistributions of source code must retain the above copyright | 
| +// notice, this list of conditions and the following disclaimer. | 
| +// * Redistributions in binary form must reproduce the above | 
| +// copyright notice, this list of conditions and the following | 
| +// disclaimer in the documentation and/or other materials provided | 
| +// with the distribution. | 
| +// * Neither the name of Google Inc. nor the names of its | 
| +// contributors may be used to endorse or promote products derived | 
| +// from this software without specific prior written permission. | 
| +// | 
| +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
| +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
| +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
| +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
| +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
| +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
| +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
| +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
| +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
| +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
| +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
| + | 
| +// Flags: --allow-natives-syntax --turbo | 
| + | 
| +// This test file was generated by tools/gen-inlining-tests.py . | 
| + | 
| +function assertOptResultEquals(expected, f) { | 
| + // f(); | 
| + // %DebugPrint(f); | 
| + eval("'dont optimize this function itself please, but do optimize f'"); | 
| + %OptimizeFunctionOnNextCall(f); | 
| + assertEquals(expected, f()); | 
| +} | 
| + | 
| +function assertOptThrowsWith(expected, f) { | 
| + // f(); | 
| + // %DebugPrint(f); | 
| + eval("'dont optimize this function itself please, but do optimize f'"); | 
| + %OptimizeFunctionOnNextCall(f); | 
| + try { | 
| + var result = f(); | 
| + fail("assertOptThrowsWith", "exception: " + expected, "result: " + result); | 
| + } catch (ex) { | 
| + assertEquals(expected, ex); | 
| + } | 
| +} | 
| + | 
| +var counter = 0; | 
| + | 
| +function increaseAndReturn15() { | 
| + counter++; | 
| + return 15; | 
| +} | 
| + | 
| +function increaseAndThrow42() { | 
| + counter++; | 
| + throw 42; | 
| +} | 
| + | 
| +function returnOrThrow(doReturn) { | 
| + if (doReturn) { | 
| + return increaseAndReturn15(); | 
| + } else { | 
| + return increaseAndThrow42(); | 
| + } | 
| +} | 
| + | 
| +// 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; | 
| +} | 
| + | 
| +""".strip() | 
| + | 
| +# Assert Python 3; reboot to Python 3 if currently on Python 2 | 
| +import sys, os | 
| +if sys.version_info[0] == 2: | 
| + sys.stderr.write("Restarting script using Python 3.\n") | 
| + os.execl("/usr/bin/env", "env", "python3", __file__) | 
| 
 
Jarin
2016/08/10 12:02:52
Would it be too hard to make this work with python
 
bgeron
2016/08/10 14:37:27
Easier than expected! Done.
 
 | 
| + | 
| +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,) | 
| + | 
| +NUM_TESTS_PRINTED = 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) | 
| + | 
| + ( | 
| + alternativeFn2, # use alternative #2 for returning/throwing. | 
| + alternativeFn1, # use alternative #1 for returning/throwing. | 
| + tryReturns, # in try block, call returning function | 
| + tryThrows, # in try block, call throwing 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 | 
| + ) = flags | 
| 
 
Jarin
2016/08/10 12:02:52
We would also like to test deoptimization. However
 
bgeron
2016/08/10 14:37:27
Ack. Let's look at this together when you're back.
 
 | 
| + | 
| + # Some pruning | 
| + | 
| + if alternativeFn1 and alternativeFn2: 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 we use an alternative function, then we don't also need to test both | 
| + # tryReturns and tryThrows at the same time. | 
| + if (alternativeFn1 or alternativeFn2) and tryReturns and tryThrows: return | 
| + | 
| + # Flag check succeeded. | 
| + global NUM_TESTS_PRINTED | 
| + NUM_TESTS_PRINTED += 1 | 
| + | 
| + trueFlagNames = [name for (name, value) in flags._asdict().items() if value] | 
| + flagsMsgLine = "// Variant flags: [{}]".format(', '.join(trueFlagNames)) | 
| + print(textwrap.fill(flagsMsgLine, subsequent_indent='// ')) | 
| + print() | 
| + | 
| + if not alternativeFn1 and not alternativeFn2: | 
| + fragments = { | 
| + 'increaseAndReturn15': 'increaseAndReturn15()', | 
| + 'increaseAndThrow42': 'increaseAndThrow42()' | 
| + } | 
| + elif alternativeFn1: | 
| + fragments = { | 
| + 'increaseAndReturn15': 'returnOrThrow(true)', | 
| + 'increaseAndThrow42': 'returnOrThrow(false)' | 
| + } | 
| + else: | 
| + assert alternativeFn2 | 
| + fragments = { | 
| + 'increaseAndReturn15': 'invertFunctionCall(increaseAndThrow42)', | 
| + 'increaseAndThrow42': 'invertFunctionCall(increaseAndReturn15)' | 
| + } | 
| + | 
| + # 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. | 
| + counter = 0 | 
| + | 
| + print( "counter = 0;") | 
| + print( "var f = function() {") | 
| + print( " var local = 3;") | 
| + local = 3 | 
| + print( " try {") | 
| + print( " counter++;") | 
| + counter += 1 | 
| + resultGoesTo = "local +=" if tryResultToLocal else "return" | 
| + if tryReturns and not (tryThrows and not tryFirstReturns): | 
| + print( " {} {increaseAndReturn15};".format(resultGoesTo, **fragments)) | 
| + if result == None: | 
| + counter += 1 | 
| + if tryResultToLocal: | 
| + local += 15 | 
| + else: | 
| + result = ("return", 15) | 
| + if tryThrows: | 
| + print( " {} {increaseAndThrow42};".format(resultGoesTo, **fragments)) | 
| + if result == None: | 
| + counter += 1 | 
| + result = ("throw", 42) | 
| + if tryReturns and tryThrows and not tryFirstReturns: | 
| + print( " {} {increaseAndReturn15};".format(resultGoesTo, **fragments)) | 
| + if result == None: | 
| + counter += 1 | 
| + if tryResultToLocal: | 
| + local += 15 | 
| + else: | 
| + result = ("return", 15) | 
| + print( " counter++;") | 
| + if result == None: | 
| + counter += 1 | 
| + | 
| + if doCatch: | 
| + print( " } catch (ex) {") | 
| + print( " counter++;") | 
| + if isinstance(result, tuple) and result[0] == 'throw': | 
| + counter += 1 | 
| + if catchThrows: | 
| + print(" throw 2 + ex;") | 
| + if isinstance(result, tuple) and result[0] == "throw": | 
| + result = ('throw', 2 + result[1]) | 
| + elif catchReturns and catchWithLocal: | 
| + print(" return 2 + local;") | 
| + if isinstance(result, tuple) and result[0] == "throw": | 
| + result = ('return', 2 + local) | 
| + elif catchReturns and not catchWithLocal: | 
| + print(" return 2 + ex;"); | 
| + if isinstance(result, tuple) and result[0] == "throw": | 
| + result = ('return', 2 + result[1]) | 
| + elif catchWithLocal: | 
| + print(" 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 | 
| + print( " counter++;") | 
| + | 
| + if doFinally: | 
| + print( " } finally {") | 
| + print( " counter++;") | 
| + counter += 1 | 
| + if finallyThrows: | 
| + print(" throw 25;") | 
| + result = ('throw', 25) | 
| + elif finallyReturns: | 
| + print(" return 3 + local;") | 
| + result = ('return', 3 + local) | 
| + elif not finallyReturns and not finallyThrows: | 
| + print(" local += 2;") | 
| + local += 2 | 
| + counter += 1 | 
| + else: assert False # unreachable | 
| + print( " counter++;") | 
| + | 
| + print( " }") | 
| + print( " counter++;") | 
| + if result == None: | 
| + counter += 1 | 
| + if endReturnLocal: | 
| + print( " return 5 + local;") | 
| + if result == None: | 
| + result = ('return', 5 + local) | 
| + print( "}") | 
| + | 
| + if result == None: | 
| + print( "assertOptResultEquals(undefined, f);") | 
| + else: | 
| + tag, value = result | 
| + if tag == "return": | 
| + print( "assertOptResultEquals({}, f);".format(value)) | 
| + else: | 
| + assert tag == "throw" | 
| + print( "assertOptThrowsWith({}, f);".format(value)) | 
| + | 
| + print( "assertEquals({}, counter);".format(counter)) | 
| + print( ) | 
| + | 
| + | 
| +flagtuple = namedtuple('flagtuple', ( | 
| + "alternativeFn2", | 
| + "alternativeFn1", | 
| + "tryReturns", | 
| + "tryThrows", | 
| + "tryFirstReturns", | 
| + "tryResultToLocal", | 
| + "doCatch", | 
| + "catchReturns", | 
| + "catchWithLocal", | 
| + "catchThrows", | 
| + "doFinally", | 
| + "finallyReturns", | 
| + "finallyThrows", | 
| + "endReturnLocal", | 
| + )) | 
| + | 
| +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__': | 
| + print(PREAMBLE) | 
| + print() | 
| + for flags in allFlagCombinations: | 
| + printtest(flags) | 
| + | 
| + print("// Total: {} tests.".format(NUM_TESTS_PRINTED)) |