|  | #!/usr/bin/python | 
|  | # Copyright 2017 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. | 
|  |  | 
|  | # for py2/py3 compatibility | 
|  | from __future__ import print_function | 
|  |  | 
|  | import argparse | 
|  | import math | 
|  | import multiprocessing | 
|  | import os | 
|  | import random | 
|  | import subprocess | 
|  | import sys | 
|  | import tempfile | 
|  |  | 
|  | # Configuration. | 
|  | kChars = "0123456789abcdef" | 
|  | kBase = 16 | 
|  | kLineLength = 70  # A bit less than 80. | 
|  | kNumInputsGenerate = 20 | 
|  | kNumInputsStress = 1000 | 
|  |  | 
|  | # Internally used sentinels. | 
|  | kNo = 0 | 
|  | kYes = 1 | 
|  | kRandom = 2 | 
|  |  | 
|  | TEST_HEADER = """\ | 
|  | // Copyright 2017 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. | 
|  |  | 
|  | // Generated by %s. | 
|  | """ % sys.argv[0] | 
|  |  | 
|  | TEST_BODY = """ | 
|  | var error_count = 0; | 
|  | for (var i = 0; i < data.length; i++) { | 
|  | var d = data[i]; | 
|  | %s | 
|  | } | 
|  | if (error_count !== 0) { | 
|  | print("Finished with " + error_count + " errors.") | 
|  | quit(1); | 
|  | }""" | 
|  |  | 
|  | def GenRandom(length, negative=kRandom): | 
|  | if length == 0: return "0n" | 
|  | s = [] | 
|  | if negative == kYes or (negative == kRandom and (random.randint(0, 1) == 0)): | 
|  | s.append("-")  # 50% chance of negative. | 
|  | s.append("0x") | 
|  | s.append(kChars[random.randint(1, kBase - 1)])  # No leading zero. | 
|  | for i in range(1, length): | 
|  | s.append(kChars[random.randint(0, kBase - 1)]) | 
|  | s.append("n") | 
|  | return "".join(s) | 
|  |  | 
|  | def Parse(x): | 
|  | assert x[-1] == 'n', x | 
|  | return int(x[:-1], kBase) | 
|  |  | 
|  | def Format(x): | 
|  | original = x | 
|  | negative = False | 
|  | if x == 0: return "0n" | 
|  | if x < 0: | 
|  | negative = True | 
|  | x = -x | 
|  | s = "" | 
|  | while x > 0: | 
|  | s = kChars[x % kBase] + s | 
|  | x = x / kBase | 
|  | s = "0x" + s + "n" | 
|  | if negative: | 
|  | s = "-" + s | 
|  | assert Parse(s) == original | 
|  | return s | 
|  |  | 
|  | class TestGenerator(object): | 
|  | # Subclasses must implement these. | 
|  | # Returns a JSON snippet defining inputs and expected output for one test. | 
|  | def EmitOne(self): raise NotImplementedError | 
|  | # Returns a snippet of JavaScript that will operate on a variable "d" | 
|  | # whose content is defined by the result of a call to "EmitOne". | 
|  | def EmitTestCore(self): raise NotImplementedError | 
|  |  | 
|  | def EmitHeader(self): | 
|  | return TEST_HEADER | 
|  |  | 
|  | def EmitData(self, count): | 
|  | s = [] | 
|  | for i in range(count): | 
|  | s.append(self.EmitOne()) | 
|  | return "var data = [" + ", ".join(s) + "];" | 
|  |  | 
|  | def EmitTestBody(self): | 
|  | return TEST_BODY % self.EmitTestCore() | 
|  |  | 
|  | def PrintTest(self, count): | 
|  | print(self.EmitHeader()) | 
|  | print(self.EmitData(count)) | 
|  | print(self.EmitTestBody()) | 
|  |  | 
|  | def RunTest(self, count, binary): | 
|  | try: | 
|  | fd, path = tempfile.mkstemp(suffix=".js", prefix="bigint-test-") | 
|  | with open(path, "w") as f: | 
|  | f.write(self.EmitData(count)) | 
|  | f.write(self.EmitTestBody()) | 
|  | return subprocess.call("%s %s" % (binary, path), | 
|  | shell=True) | 
|  | finally: | 
|  | os.close(fd) | 
|  | os.remove(path) | 
|  |  | 
|  | class UnaryOp(TestGenerator): | 
|  | # Subclasses must implement these two. | 
|  | def GetOpString(self): raise NotImplementedError | 
|  | def GenerateResult(self, x): raise NotImplementedError | 
|  |  | 
|  | # Subclasses may override this: | 
|  | def GenerateInput(self): | 
|  | return GenRandom(random.randint(0, kLineLength)) | 
|  |  | 
|  | # Subclasses should not override anything below. | 
|  | def EmitOne(self): | 
|  | x_str = self.GenerateInput() | 
|  | x_num = Parse(x_str) | 
|  | result_num = self.GenerateResult(x_num) | 
|  | result_str = Format(result_num) | 
|  | return "{\n  a: %s,\n  r: %s\n}" % (x_str, result_str) | 
|  |  | 
|  | def EmitTestCore(self): | 
|  | return """\ | 
|  | var r = %(op)sd.a; | 
|  | if (d.r !== r) { | 
|  | print("Input:    " + d.a.toString(%(base)d)); | 
|  | print("Result:   " + r.toString(%(base)d)); | 
|  | print("Expected: " + d.r); | 
|  | error_count++; | 
|  | }""" % {"op": self.GetOpString(), "base": kBase} | 
|  |  | 
|  | class BinaryOp(TestGenerator): | 
|  | # Subclasses must implement these two. | 
|  | def GetOpString(self): raise NotImplementedError | 
|  | def GenerateResult(self, left, right): raise NotImplementedError | 
|  |  | 
|  | # Subclasses may override these: | 
|  | def GenerateInputLengths(self): | 
|  | return random.randint(0, kLineLength), random.randint(0, kLineLength) | 
|  |  | 
|  | def GenerateInputs(self): | 
|  | left_length, right_length = self.GenerateInputLengths() | 
|  | return GenRandom(left_length), GenRandom(right_length) | 
|  |  | 
|  | # Subclasses should not override anything below. | 
|  | def EmitOne(self): | 
|  | left_str, right_str = self.GenerateInputs() | 
|  | left_num = Parse(left_str) | 
|  | right_num = Parse(right_str) | 
|  | result_num = self.GenerateResult(left_num, right_num) | 
|  | result_str = Format(result_num) | 
|  | return ("{\n  a: %s,\n  b: %s,\n  r: %s\n}" % | 
|  | (left_str, right_str, result_str)) | 
|  |  | 
|  | def EmitTestCore(self): | 
|  | return """\ | 
|  | var r = d.a %(op)s d.b; | 
|  | if (d.r !== r) { | 
|  | print("Input A:  " + d.a.toString(%(base)d)); | 
|  | print("Input B:  " + d.b.toString(%(base)d)); | 
|  | print("Result:   " + r.toString(%(base)d)); | 
|  | print("Expected: " + d.r); | 
|  | print("Op: %(op)s"); | 
|  | error_count++; | 
|  | }""" % {"op": self.GetOpString(), "base": kBase} | 
|  |  | 
|  | class Neg(UnaryOp): | 
|  | def GetOpString(self): return "-" | 
|  | def GenerateResult(self, x): return -x | 
|  |  | 
|  | class BitNot(UnaryOp): | 
|  | def GetOpString(self): return "~" | 
|  | def GenerateResult(self, x): return ~x | 
|  |  | 
|  | class Inc(UnaryOp): | 
|  | def GetOpString(self): return "++" | 
|  | def GenerateResult(self, x): return x + 1 | 
|  |  | 
|  | class Dec(UnaryOp): | 
|  | def GetOpString(self): return "--" | 
|  | def GenerateResult(self, x): return x - 1 | 
|  |  | 
|  | class Add(BinaryOp): | 
|  | def GetOpString(self): return "+" | 
|  | def GenerateResult(self, a, b): return a + b | 
|  |  | 
|  | class Sub(BinaryOp): | 
|  | def GetOpString(self): return "-" | 
|  | def GenerateResult(self, a, b): return a - b | 
|  |  | 
|  | class Mul(BinaryOp): | 
|  | def GetOpString(self): return "*" | 
|  | def GenerateResult(self, a, b): return a * b | 
|  | def GenerateInputLengths(self): | 
|  | left_length = random.randint(1, kLineLength) | 
|  | return left_length, kLineLength - left_length | 
|  |  | 
|  | class Div(BinaryOp): | 
|  | def GetOpString(self): return "/" | 
|  | def GenerateResult(self, a, b): | 
|  | result = abs(a) / abs(b) | 
|  | if (a < 0) != (b < 0): result = -result | 
|  | return result | 
|  | def GenerateInputLengths(self): | 
|  | # Let the left side be longer than the right side with high probability, | 
|  | # because that case is more interesting. | 
|  | min_left = kLineLength * 6 / 10 | 
|  | max_right = kLineLength * 7 / 10 | 
|  | return random.randint(min_left, kLineLength), random.randint(1, max_right) | 
|  |  | 
|  | class Mod(Div):  # Sharing GenerateInputLengths. | 
|  | def GetOpString(self): return "%" | 
|  | def GenerateResult(self, a, b): | 
|  | result = a % b | 
|  | if a < 0 and result > 0: | 
|  | result -= abs(b) | 
|  | if a > 0 and result < 0: | 
|  | result += abs(b) | 
|  | return result | 
|  |  | 
|  | class Shl(BinaryOp): | 
|  | def GetOpString(self): return "<<" | 
|  | def GenerateInputsInternal(self, small_shift_positive): | 
|  | left_length = random.randint(0, kLineLength - 1) | 
|  | left = GenRandom(left_length) | 
|  | small_shift = random.randint(0, 1) == 0 | 
|  | if small_shift: | 
|  | right_length = 1 + int(math.log((kLineLength - left_length), kBase)) | 
|  | neg = kNo if small_shift_positive else kYes | 
|  | else: | 
|  | right_length = random.randint(0, 3) | 
|  | neg = kYes if small_shift_positive else kNo | 
|  | right = GenRandom(right_length, negative=neg) | 
|  | return left, right | 
|  |  | 
|  | def GenerateInputs(self): return self.GenerateInputsInternal(True) | 
|  | def GenerateResult(self, a, b): | 
|  | if b < 0: return a >> -b | 
|  | return a << b | 
|  |  | 
|  | class Sar(Shl):  # Sharing GenerateInputsInternal. | 
|  | def GetOpString(self): return ">>" | 
|  | def GenerateInputs(self): | 
|  | return self.GenerateInputsInternal(False) | 
|  | def GenerateResult(self, a, b): | 
|  | if b < 0: return a << -b | 
|  | return a >> b | 
|  |  | 
|  | class BitAnd(BinaryOp): | 
|  | def GetOpString(self): return "&" | 
|  | def GenerateResult(self, a, b): return a & b | 
|  |  | 
|  | class BitOr(BinaryOp): | 
|  | def GetOpString(self): return "|" | 
|  | def GenerateResult(self, a, b): return a | b | 
|  |  | 
|  | class BitXor(BinaryOp): | 
|  | def GetOpString(self): return "^" | 
|  | def GenerateResult(self, a, b): return a ^ b | 
|  |  | 
|  | OPS = { | 
|  | "add": Add, | 
|  | "sub": Sub, | 
|  | "mul": Mul, | 
|  | "div": Div, | 
|  | "mod": Mod, | 
|  | "inc": Inc, | 
|  | "dec": Dec, | 
|  | "neg": Neg, | 
|  | "not": BitNot, | 
|  | "shl": Shl, | 
|  | "sar": Sar, | 
|  | "and": BitAnd, | 
|  | "or": BitOr, | 
|  | "xor": BitXor | 
|  | } | 
|  |  | 
|  | OPS_NAMES = ", ".join(sorted(OPS.keys())) | 
|  |  | 
|  | def RunOne(op, num_inputs, binary): | 
|  | return OPS[op]().RunTest(num_inputs, binary) | 
|  | def WrapRunOne(args): | 
|  | return RunOne(*args) | 
|  | def RunAll(args): | 
|  | for op in args.op: | 
|  | for r in range(args.runs): | 
|  | yield (op, args.num_inputs, args.binary) | 
|  |  | 
|  | def Main(): | 
|  | parser = argparse.ArgumentParser( | 
|  | description="Helper for generating or running BigInt tests.") | 
|  | parser.add_argument( | 
|  | "action", help="Action to perform: 'generate' or 'stress'") | 
|  | parser.add_argument( | 
|  | "op", nargs="+", | 
|  | help="Operation(s) to test, one or more of: %s. In 'stress' mode, " | 
|  | "special op 'all' tests all ops." % OPS_NAMES) | 
|  | parser.add_argument( | 
|  | "-n", "--num-inputs", type=int, default=-1, | 
|  | help="Number of input/output sets in each generated test. Defaults to " | 
|  | "%d for 'generate' and '%d' for 'stress' mode." % | 
|  | (kNumInputsGenerate, kNumInputsStress)) | 
|  |  | 
|  | stressopts = parser.add_argument_group("'stress' mode arguments") | 
|  | stressopts.add_argument( | 
|  | "-r", "--runs", type=int, default=1000, | 
|  | help="Number of tests (with NUM_INPUTS each) to generate and run. " | 
|  | "Default: %(default)s.") | 
|  | stressopts.add_argument( | 
|  | "-b", "--binary", default="out/x64.debug/d8", | 
|  | help="The 'd8' binary to use. Default: %(default)s.") | 
|  | args = parser.parse_args() | 
|  |  | 
|  | for op in args.op: | 
|  | if op not in OPS.keys() and op != "all": | 
|  | print("Invalid op '%s'. See --help." % op) | 
|  | return 1 | 
|  |  | 
|  | if len(args.op) == 1 and args.op[0] == "all": | 
|  | args.op = OPS.keys() | 
|  |  | 
|  | if args.action == "generate": | 
|  | if args.num_inputs < 0: args.num_inputs = kNumInputsGenerate | 
|  | for op in args.op: | 
|  | OPS[op]().PrintTest(args.num_inputs) | 
|  | elif args.action == "stress": | 
|  | if args.num_inputs < 0: args.num_inputs = kNumInputsStress | 
|  | result = 0 | 
|  | pool = multiprocessing.Pool(multiprocessing.cpu_count()) | 
|  | for r in pool.imap_unordered(WrapRunOne, RunAll(args)): | 
|  | result = result or r | 
|  | return result | 
|  | else: | 
|  | print("Invalid action '%s'. See --help." % args.action) | 
|  | return 1 | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | sys.exit(Main()) |