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