v8/tools/bigint-tester.py

353 lines
10 KiB
Python
Raw Normal View History

#!/usr/bin/python3
# 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())