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