| 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..179b4ad3255415888062778c5929c8b949231dab
|
| --- /dev/null
|
| +++ b/tools/gen-inlining-tests.py
|
| @@ -0,0 +1,510 @@
|
| +#!/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 = 94
|
| +
|
| +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 --turbo --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 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;
|
| +}
|
| +
|
| +function increaseAndStore15Constructor() {
|
| + if (deopt) %DeoptimizeFunction(f);
|
| + ++counter;
|
| + this.x = 15;
|
| +}
|
| +
|
| +function increaseAndThrow42Constructor() {
|
| + if (deopt) %DeoptimizeFunction(f);
|
| + ++counter;
|
| + this.x = 42;
|
| + throw this.x;
|
| +}
|
| +
|
| +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, (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,)
|
| +
|
| +FLAGLETTERS="4321trflcrltfrtld"
|
| +
|
| +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.
|
| + (
|
| + alternativeFn4, # use alternative #4 for returning/throwing.
|
| + alternativeFn3, # use alternative #3 for returning/throwing.
|
| + alternativeFn2, # use alternative #2 for returning/throwing.
|
| + alternativeFn1, # use alternative #1 for returning/throwing.
|
| + 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 > 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])
|
| + rareAlternative = any([alternativeFn1, alternativeFn3, alternativeFn4])
|
| +
|
| + # 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.
|
| + if ((tryResultToLocal or catchWithLocal or endReturnLocal) and
|
| + not alternativeFn2):
|
| + return
|
| + # We don't need to test deopting into a finally.
|
| + if doFinally and 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': 'invertFunctionCall(increaseAndThrow42)',
|
| + 'increaseAndThrow42': 'invertFunctionCall(increaseAndReturn15)',
|
| + }
|
| + elif alternativeFn3:
|
| + fragments = {
|
| + 'increaseAndReturn15': '(new increaseAndStore15Constructor()).x',
|
| + 'increaseAndThrow42': '(new increaseAndThrow42Constructor()).x',
|
| + }
|
| + else:
|
| + assert alternativeFn4
|
| + 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 = 3;")
|
| + write( " deopt = {};".format("true" if deopt else "false"))
|
| + local = 3
|
| + write( " try {")
|
| + write( " counter++;")
|
| + counter += 1
|
| + resultTo = "local +=" if tryResultToLocal else "return"
|
| + if tryReturns and not (tryThrows and not tryFirstReturns):
|
| + write( " {} {increaseAndReturn15};".format(resultTo, **fragments))
|
| + if result == None:
|
| + counter += 1
|
| + if tryResultToLocal:
|
| + local += 15
|
| + else:
|
| + result = ("return", 15)
|
| + if tryThrows:
|
| + write( " {} {increaseAndThrow42};".format(resultTo, **fragments))
|
| + if result == None:
|
| + counter += 1
|
| + result = ("throw", 42)
|
| + if tryReturns and tryThrows and not tryFirstReturns:
|
| + write( " {} {increaseAndReturn15};".format(resultTo, **fragments))
|
| + if result == None:
|
| + counter += 1
|
| + if tryResultToLocal:
|
| + local += 15
|
| + else:
|
| + result = ("return", 15)
|
| + 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();")
|
| +
|
| +
|
| +flagtuple = namedtuple('flagtuple', (
|
| + "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()
|
|
|