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