1#!/usr/bin/python
2# Copyright 2017 the V8 project authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
7import math
8import multiprocessing
9import os
10import random
11import subprocess
12import sys
13import tempfile
14
15# Configuration.
16kChars = "0123456789abcdef"
17kBase = 16
18kLineLength = 70  # A bit less than 80.
19kNumInputsGenerate = 20
20kNumInputsStress = 1000
21
22# Internally used sentinels.
23kNo = 0
24kYes = 1
25kRandom = 2
26
27TEST_HEADER = """\
28// Copyright 2017 the V8 project authors. All rights reserved.
29// Use of this source code is governed by a BSD-style license that can be
30// found in the LICENSE file.
31
32// Generated by %s.
33
34// Flags: --harmony-bigint
35""" % sys.argv[0]
36
37TEST_BODY = """
38var error_count = 0;
39for (var i = 0; i < data.length; i++) {
40  var d = data[i];
41%s
42}
43if (error_count !== 0) {
44  print("Finished with " + error_count + " errors.")
45  quit(1);
46}"""
47
48def GenRandom(length, negative=kRandom):
49  if length == 0: return "0n"
50  s = []
51  if negative == kYes or (negative == kRandom and (random.randint(0, 1) == 0)):
52    s.append("-")  # 50% chance of negative.
53  s.append("0x")
54  s.append(kChars[random.randint(1, kBase - 1)])  # No leading zero.
55  for i in range(1, length):
56    s.append(kChars[random.randint(0, kBase - 1)])
57  s.append("n")
58  return "".join(s)
59
60def Parse(x):
61  assert x[-1] == 'n', x
62  return int(x[:-1], kBase)
63
64def Format(x):
65  original = x
66  negative = False
67  if x == 0: return "0n"
68  if x < 0:
69    negative = True
70    x = -x
71  s = ""
72  while x > 0:
73    s = kChars[x % kBase] + s
74    x = x / kBase
75  s = "0x" + s + "n"
76  if negative:
77    s = "-" + s
78  assert Parse(s) == original
79  return s
80
81class TestGenerator(object):
82  # Subclasses must implement these.
83  # Returns a JSON snippet defining inputs and expected output for one test.
84  def EmitOne(self): raise NotImplementedError
85  # Returns a snippet of JavaScript that will operate on a variable "d"
86  # whose content is defined by the result of a call to "EmitOne".
87  def EmitTestCore(self): raise NotImplementedError
88
89  def EmitHeader(self):
90    return TEST_HEADER
91
92  def EmitData(self, count):
93    s = []
94    for i in range(count):
95      s.append(self.EmitOne())
96    return "var data = [" + ", ".join(s) + "];"
97
98  def EmitTestBody(self):
99    return TEST_BODY % self.EmitTestCore()
100
101  def PrintTest(self, count):
102    print(self.EmitHeader())
103    print(self.EmitData(count))
104    print(self.EmitTestBody())
105
106  def RunTest(self, count, binary):
107    try:
108      fd, path = tempfile.mkstemp(suffix=".js", prefix="bigint-test-")
109      with open(path, "w") as f:
110        f.write(self.EmitData(count))
111        f.write(self.EmitTestBody())
112      return subprocess.call("%s --harmony-bigint %s" % (binary, path),
113                             shell=True)
114    finally:
115      os.close(fd)
116      os.remove(path)
117
118class UnaryOp(TestGenerator):
119  # Subclasses must implement these two.
120  def GetOpString(self): raise NotImplementedError
121  def GenerateResult(self, x): raise NotImplementedError
122
123  # Subclasses may override this:
124  def GenerateInput(self):
125    return GenRandom(random.randint(0, kLineLength))
126
127  # Subclasses should not override anything below.
128  def EmitOne(self):
129    x_str = self.GenerateInput()
130    x_num = Parse(x_str)
131    result_num = self.GenerateResult(x_num)
132    result_str = Format(result_num)
133    return "{\n  a: %s,\n  r: %s\n}" % (x_str, result_str)
134
135  def EmitTestCore(self):
136    return """\
137  var r = %(op)sd.a;
138  if (d.r !== r) {
139    print("Input:    " + d.a.toString(%(base)d));
140    print("Result:   " + r.toString(%(base)d));
141    print("Expected: " + d.r);
142    error_count++;
143  }""" % {"op": self.GetOpString(), "base": kBase}
144
145class BinaryOp(TestGenerator):
146  # Subclasses must implement these two.
147  def GetOpString(self): raise NotImplementedError
148  def GenerateResult(self, left, right): raise NotImplementedError
149
150  # Subclasses may override these:
151  def GenerateInputLengths(self):
152    return random.randint(0, kLineLength), random.randint(0, kLineLength)
153
154  def GenerateInputs(self):
155    left_length, right_length = self.GenerateInputLengths()
156    return GenRandom(left_length), GenRandom(right_length)
157
158  # Subclasses should not override anything below.
159  def EmitOne(self):
160    left_str, right_str = self.GenerateInputs()
161    left_num = Parse(left_str)
162    right_num = Parse(right_str)
163    result_num = self.GenerateResult(left_num, right_num)
164    result_str = Format(result_num)
165    return ("{\n  a: %s,\n  b: %s,\n  r: %s\n}" %
166            (left_str, right_str, result_str))
167
168  def EmitTestCore(self):
169    return """\
170  var r = d.a %(op)s d.b;
171  if (d.r !== r) {
172    print("Input A:  " + d.a.toString(%(base)d));
173    print("Input B:  " + d.b.toString(%(base)d));
174    print("Result:   " + r.toString(%(base)d));
175    print("Expected: " + d.r);
176    print("Op: %(op)s");
177    error_count++;
178  }""" % {"op": self.GetOpString(), "base": kBase}
179
180class Neg(UnaryOp):
181  def GetOpString(self): return "-"
182  def GenerateResult(self, x): return -x
183
184class BitNot(UnaryOp):
185  def GetOpString(self): return "~"
186  def GenerateResult(self, x): return ~x
187
188class Inc(UnaryOp):
189  def GetOpString(self): return "++"
190  def GenerateResult(self, x): return x + 1
191
192class Dec(UnaryOp):
193  def GetOpString(self): return "--"
194  def GenerateResult(self, x): return x - 1
195
196class Add(BinaryOp):
197  def GetOpString(self): return "+"
198  def GenerateResult(self, a, b): return a + b
199
200class Sub(BinaryOp):
201  def GetOpString(self): return "-"
202  def GenerateResult(self, a, b): return a - b
203
204class Mul(BinaryOp):
205  def GetOpString(self): return "*"
206  def GenerateResult(self, a, b): return a * b
207  def GenerateInputLengths(self):
208    left_length = random.randint(1, kLineLength)
209    return left_length, kLineLength - left_length
210
211class Div(BinaryOp):
212  def GetOpString(self): return "/"
213  def GenerateResult(self, a, b):
214    result = abs(a) / abs(b)
215    if (a < 0) != (b < 0): result = -result
216    return result
217  def GenerateInputLengths(self):
218    # Let the left side be longer than the right side with high probability,
219    # because that case is more interesting.
220    min_left = kLineLength * 6 / 10
221    max_right = kLineLength * 7 / 10
222    return random.randint(min_left, kLineLength), random.randint(1, max_right)
223
224class Mod(Div):  # Sharing GenerateInputLengths.
225  def GetOpString(self): return "%"
226  def GenerateResult(self, a, b):
227    result = a % b
228    if a < 0 and result > 0:
229      result -= abs(b)
230    if a > 0 and result < 0:
231      result += abs(b)
232    return result
233
234class Shl(BinaryOp):
235  def GetOpString(self): return "<<"
236  def GenerateInputsInternal(self, small_shift_positive):
237    left_length = random.randint(0, kLineLength - 1)
238    left = GenRandom(left_length)
239    small_shift = random.randint(0, 1) == 0
240    if small_shift:
241      right_length = 1 + int(math.log((kLineLength - left_length), kBase))
242      neg = kNo if small_shift_positive else kYes
243    else:
244      right_length = random.randint(0, 3)
245      neg = kYes if small_shift_positive else kNo
246    right = GenRandom(right_length, negative=neg)
247    return left, right
248
249  def GenerateInputs(self): return self.GenerateInputsInternal(True)
250  def GenerateResult(self, a, b):
251    if b < 0: return a >> -b
252    return a << b
253
254class Sar(Shl):  # Sharing GenerateInputsInternal.
255  def GetOpString(self): return ">>"
256  def GenerateInputs(self):
257    return self.GenerateInputsInternal(False)
258  def GenerateResult(self, a, b):
259    if b < 0: return a << -b
260    return a >> b
261
262class BitAnd(BinaryOp):
263  def GetOpString(self): return "&"
264  def GenerateResult(self, a, b): return a & b
265
266class BitOr(BinaryOp):
267  def GetOpString(self): return "|"
268  def GenerateResult(self, a, b): return a | b
269
270class BitXor(BinaryOp):
271  def GetOpString(self): return "^"
272  def GenerateResult(self, a, b): return a ^ b
273
274OPS = {
275  "add": Add,
276  "sub": Sub,
277  "mul": Mul,
278  "div": Div,
279  "mod": Mod,
280  "inc": Inc,
281  "dec": Dec,
282  "neg": Neg,
283  "not": BitNot,
284  "shl": Shl,
285  "sar": Sar,
286  "and": BitAnd,
287  "or": BitOr,
288  "xor": BitXor
289}
290
291OPS_NAMES = ", ".join(sorted(OPS.keys()))
292
293def RunOne(op, num_inputs, binary):
294  return OPS[op]().RunTest(num_inputs, binary)
295def WrapRunOne(args):
296  return RunOne(*args)
297def RunAll(args):
298  for op in args.op:
299    for r in xrange(args.runs):
300      yield (op, args.num_inputs, args.binary)
301
302def Main():
303  parser = argparse.ArgumentParser(
304      description="Helper for generating or running BigInt tests.")
305  parser.add_argument(
306      "action", help="Action to perform: 'generate' or 'stress'")
307  parser.add_argument(
308      "op", nargs="+",
309      help="Operation(s) to test, one or more of: %s. In 'stress' mode, "
310           "special op 'all' tests all ops." % OPS_NAMES)
311  parser.add_argument(
312      "-n", "--num-inputs", type=int, default=-1,
313      help="Number of input/output sets in each generated test. Defaults to "
314           "%d for 'generate' and '%d' for 'stress' mode." %
315           (kNumInputsGenerate, kNumInputsStress))
316
317  stressopts = parser.add_argument_group("'stress' mode arguments")
318  stressopts.add_argument(
319      "-r", "--runs", type=int, default=1000,
320      help="Number of tests (with NUM_INPUTS each) to generate and run. "
321           "Default: %(default)s.")
322  stressopts.add_argument(
323      "-b", "--binary", default="out/x64.debug/d8",
324      help="The 'd8' binary to use. Default: %(default)s.")
325  args = parser.parse_args()
326
327  for op in args.op:
328    if op not in OPS.keys() and op != "all":
329      print("Invalid op '%s'. See --help." % op)
330      return 1
331
332  if len(args.op) == 1 and args.op[0] == "all":
333    args.op = OPS.keys()
334
335  if args.action == "generate":
336    if args.num_inputs < 0: args.num_inputs = kNumInputsGenerate
337    for op in args.op:
338      OPS[op]().PrintTest(args.num_inputs)
339  elif args.action == "stress":
340    if args.num_inputs < 0: args.num_inputs = kNumInputsStress
341    result = 0
342    pool = multiprocessing.Pool(multiprocessing.cpu_count())
343    for r in pool.imap_unordered(WrapRunOne, RunAll(args)):
344      result = result or r
345    return result
346  else:
347    print("Invalid action '%s'. See --help." % args.action)
348    return 1
349
350if __name__ == "__main__":
351  sys.exit(Main())
352