Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | # vim: set ts=8 sts=4 et sw=4 tw=99: |
michael@0 | 2 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 5 | |
michael@0 | 6 | import os, re |
michael@0 | 7 | import tempfile |
michael@0 | 8 | import subprocess |
michael@0 | 9 | import sys, math |
michael@0 | 10 | import datetime |
michael@0 | 11 | import random |
michael@0 | 12 | |
michael@0 | 13 | def realpath(k): |
michael@0 | 14 | return os.path.realpath(os.path.normpath(k)) |
michael@0 | 15 | |
michael@0 | 16 | class UCTNode: |
michael@0 | 17 | def __init__(self, loop): |
michael@0 | 18 | self.children = None |
michael@0 | 19 | self.loop = loop |
michael@0 | 20 | self.visits = 1 |
michael@0 | 21 | self.score = 0 |
michael@0 | 22 | |
michael@0 | 23 | def addChild(self, child): |
michael@0 | 24 | if self.children == None: |
michael@0 | 25 | self.children = [] |
michael@0 | 26 | self.children.append(child) |
michael@0 | 27 | |
michael@0 | 28 | def computeUCB(self, coeff): |
michael@0 | 29 | return (self.score / self.visits) + math.sqrt(coeff / self.visits) |
michael@0 | 30 | |
michael@0 | 31 | class UCT: |
michael@0 | 32 | def __init__(self, benchmark, bestTime, enableLoops, loops, fd, playouts): |
michael@0 | 33 | self.bm = benchmark |
michael@0 | 34 | self.fd = fd |
michael@0 | 35 | self.numPlayouts = playouts |
michael@0 | 36 | self.maxNodes = self.numPlayouts * 20 |
michael@0 | 37 | self.loops = loops |
michael@0 | 38 | self.enableLoops = enableLoops |
michael@0 | 39 | self.maturityThreshold = 20 |
michael@0 | 40 | self.originalBest = bestTime |
michael@0 | 41 | self.bestTime = bestTime |
michael@0 | 42 | self.bias = 20 |
michael@0 | 43 | self.combos = [] |
michael@0 | 44 | self.zobrist = { } |
michael@0 | 45 | random.seed() |
michael@0 | 46 | |
michael@0 | 47 | def expandNode(self, node, pending): |
michael@0 | 48 | for loop in pending: |
michael@0 | 49 | node.addChild(UCTNode(loop)) |
michael@0 | 50 | self.numNodes += 1 |
michael@0 | 51 | if self.numNodes >= self.maxNodes: |
michael@0 | 52 | return False |
michael@0 | 53 | return True |
michael@0 | 54 | |
michael@0 | 55 | def findBestChild(self, node): |
michael@0 | 56 | coeff = self.bias * math.log(node.visits) |
michael@0 | 57 | bestChild = None |
michael@0 | 58 | bestUCB = -float('Infinity') |
michael@0 | 59 | |
michael@0 | 60 | for child in node.children: |
michael@0 | 61 | ucb = child.computeUCB(coeff) |
michael@0 | 62 | if ucb >= bestUCB: |
michael@0 | 63 | bestUCB = ucb |
michael@0 | 64 | bestChild = child |
michael@0 | 65 | |
michael@0 | 66 | return child |
michael@0 | 67 | |
michael@0 | 68 | def playout(self, history): |
michael@0 | 69 | queue = [] |
michael@0 | 70 | for i in range(0, len(self.loops)): |
michael@0 | 71 | queue.append(random.randint(0, 1)) |
michael@0 | 72 | for node in history: |
michael@0 | 73 | queue[node.loop] = not self.enableLoops |
michael@0 | 74 | zash = 0 |
michael@0 | 75 | for i in range(0, len(queue)): |
michael@0 | 76 | if queue[i]: |
michael@0 | 77 | zash |= (1 << i) |
michael@0 | 78 | if zash in self.zobrist: |
michael@0 | 79 | return self.zobrist[zash] |
michael@0 | 80 | |
michael@0 | 81 | self.bm.generateBanList(self.loops, queue) |
michael@0 | 82 | result = self.bm.treeSearchRun(self.fd, ['-m', '-j'], 3) |
michael@0 | 83 | self.zobrist[zash] = result |
michael@0 | 84 | return result |
michael@0 | 85 | |
michael@0 | 86 | def step(self, loopList): |
michael@0 | 87 | node = self.root |
michael@0 | 88 | pending = loopList[:] |
michael@0 | 89 | history = [node] |
michael@0 | 90 | |
michael@0 | 91 | while True: |
michael@0 | 92 | # If this is a leaf node... |
michael@0 | 93 | if node.children == None: |
michael@0 | 94 | # And the leaf node is mature... |
michael@0 | 95 | if node.visits >= self.maturityThreshold: |
michael@0 | 96 | # If the node can be expanded, keep spinning. |
michael@0 | 97 | if self.expandNode(node, pending) and node.children != None: |
michael@0 | 98 | continue |
michael@0 | 99 | |
michael@0 | 100 | # Otherwise, this is a leaf node. Run a playout. |
michael@0 | 101 | score = self.playout(history) |
michael@0 | 102 | break |
michael@0 | 103 | |
michael@0 | 104 | # Find the best child. |
michael@0 | 105 | node = self.findBestChild(node) |
michael@0 | 106 | history.append(node) |
michael@0 | 107 | pending.remove(node.loop) |
michael@0 | 108 | |
michael@0 | 109 | # Normalize the score. |
michael@0 | 110 | origScore = score |
michael@0 | 111 | score = (self.originalBest - score) / self.originalBest |
michael@0 | 112 | |
michael@0 | 113 | for node in history: |
michael@0 | 114 | node.visits += 1 |
michael@0 | 115 | node.score += score |
michael@0 | 116 | |
michael@0 | 117 | if int(origScore) < int(self.bestTime): |
michael@0 | 118 | print('New best score: {0:f}ms'.format(origScore)) |
michael@0 | 119 | self.combos = [history] |
michael@0 | 120 | self.bestTime = origScore |
michael@0 | 121 | elif int(origScore) == int(self.bestTime): |
michael@0 | 122 | self.combos.append(history) |
michael@0 | 123 | |
michael@0 | 124 | def run(self): |
michael@0 | 125 | loopList = [i for i in range(0, len(self.loops))] |
michael@0 | 126 | self.numNodes = 1 |
michael@0 | 127 | self.root = UCTNode(-1) |
michael@0 | 128 | self.expandNode(self.root, loopList) |
michael@0 | 129 | |
michael@0 | 130 | for i in range(0, self.numPlayouts): |
michael@0 | 131 | self.step(loopList) |
michael@0 | 132 | |
michael@0 | 133 | # Build the expected combination vector. |
michael@0 | 134 | combos = [ ] |
michael@0 | 135 | for combo in self.combos: |
michael@0 | 136 | vec = [ ] |
michael@0 | 137 | for i in range(0, len(self.loops)): |
michael@0 | 138 | vec.append(int(self.enableLoops)) |
michael@0 | 139 | for node in combo: |
michael@0 | 140 | vec[node.loop] = int(not self.enableLoops) |
michael@0 | 141 | combos.append(vec) |
michael@0 | 142 | |
michael@0 | 143 | return [self.bestTime, combos] |
michael@0 | 144 | |
michael@0 | 145 | class Benchmark: |
michael@0 | 146 | def __init__(self, JS, fname): |
michael@0 | 147 | self.fname = fname |
michael@0 | 148 | self.JS = JS |
michael@0 | 149 | self.stats = { } |
michael@0 | 150 | self.runList = [ ] |
michael@0 | 151 | |
michael@0 | 152 | def run(self, fd, eargs): |
michael@0 | 153 | args = [self.JS] |
michael@0 | 154 | args.extend(eargs) |
michael@0 | 155 | args.append(fd.name) |
michael@0 | 156 | return subprocess.check_output(args).decode() |
michael@0 | 157 | |
michael@0 | 158 | # self.stats[name] = { } |
michael@0 | 159 | # self.runList.append(name) |
michael@0 | 160 | # for line in output.split('\n'): |
michael@0 | 161 | # m = re.search('line (\d+): (\d+)', line) |
michael@0 | 162 | # if m: |
michael@0 | 163 | # self.stats[name][int(m.group(1))] = int(m.group(2)) |
michael@0 | 164 | # else: |
michael@0 | 165 | # m = re.search('total: (\d+)', line) |
michael@0 | 166 | # if m: |
michael@0 | 167 | # self.stats[name]['total'] = m.group(1) |
michael@0 | 168 | |
michael@0 | 169 | def winnerForLine(self, line): |
michael@0 | 170 | best = self.runList[0] |
michael@0 | 171 | bestTime = self.stats[best][line] |
michael@0 | 172 | for run in self.runList[1:]: |
michael@0 | 173 | x = self.stats[run][line] |
michael@0 | 174 | if x < bestTime: |
michael@0 | 175 | best = run |
michael@0 | 176 | bestTime = x |
michael@0 | 177 | return best |
michael@0 | 178 | |
michael@0 | 179 | def chart(self): |
michael@0 | 180 | sys.stdout.write('{0:7s}'.format('')) |
michael@0 | 181 | sys.stdout.write('{0:15s}'.format('line')) |
michael@0 | 182 | for run in self.runList: |
michael@0 | 183 | sys.stdout.write('{0:15s}'.format(run)) |
michael@0 | 184 | sys.stdout.write('{0:15s}\n'.format('best')) |
michael@0 | 185 | for c in self.counters: |
michael@0 | 186 | sys.stdout.write('{0:10d}'.format(c)) |
michael@0 | 187 | for run in self.runList: |
michael@0 | 188 | sys.stdout.write('{0:15d}'.format(self.stats[run][c])) |
michael@0 | 189 | sys.stdout.write('{0:12s}'.format('')) |
michael@0 | 190 | sys.stdout.write('{0:15s}'.format(self.winnerForLine(c))) |
michael@0 | 191 | sys.stdout.write('\n') |
michael@0 | 192 | |
michael@0 | 193 | def preprocess(self, lines, onBegin, onEnd): |
michael@0 | 194 | stack = [] |
michael@0 | 195 | counters = [] |
michael@0 | 196 | rd = open(self.fname, 'rt') |
michael@0 | 197 | for line in rd: |
michael@0 | 198 | if re.search('\/\* BEGIN LOOP \*\/', line): |
michael@0 | 199 | stack.append([len(lines), len(counters)]) |
michael@0 | 200 | counters.append([len(lines), 0]) |
michael@0 | 201 | onBegin(lines, len(lines)) |
michael@0 | 202 | elif re.search('\/\* END LOOP \*\/', line): |
michael@0 | 203 | old = stack.pop() |
michael@0 | 204 | onEnd(lines, old[0], len(lines)) |
michael@0 | 205 | counters[old[1]][1] = len(lines) |
michael@0 | 206 | else: |
michael@0 | 207 | lines.append(line) |
michael@0 | 208 | return [lines, counters] |
michael@0 | 209 | |
michael@0 | 210 | def treeSearchRun(self, fd, args, count = 5): |
michael@0 | 211 | total = 0 |
michael@0 | 212 | for i in range(0, count): |
michael@0 | 213 | output = self.run(fd, args) |
michael@0 | 214 | total += int(output) |
michael@0 | 215 | return total / count |
michael@0 | 216 | |
michael@0 | 217 | def generateBanList(self, counters, queue): |
michael@0 | 218 | if os.path.exists('/tmp/permabans'): |
michael@0 | 219 | os.unlink('/tmp/permabans') |
michael@0 | 220 | fd = open('/tmp/permabans', 'wt') |
michael@0 | 221 | for i in range(0, len(counters)): |
michael@0 | 222 | for j in range(counters[i][0], counters[i][1] + 1): |
michael@0 | 223 | fd.write('{0:d} {1:d}\n'.format(j, int(queue[i]))) |
michael@0 | 224 | fd.close() |
michael@0 | 225 | |
michael@0 | 226 | def internalExhaustiveSearch(self, params): |
michael@0 | 227 | counters = params['counters'] |
michael@0 | 228 | |
michael@0 | 229 | # iterative algorithm to explore every combination |
michael@0 | 230 | ncombos = 2 ** len(counters) |
michael@0 | 231 | queue = [] |
michael@0 | 232 | for c in counters: |
michael@0 | 233 | queue.append(0) |
michael@0 | 234 | |
michael@0 | 235 | fd = params['fd'] |
michael@0 | 236 | bestTime = float('Infinity') |
michael@0 | 237 | bestCombos = [] |
michael@0 | 238 | |
michael@0 | 239 | i = 0 |
michael@0 | 240 | while i < ncombos: |
michael@0 | 241 | temp = i |
michael@0 | 242 | for j in range(0, len(counters)): |
michael@0 | 243 | queue[j] = temp & 1 |
michael@0 | 244 | temp = temp >> 1 |
michael@0 | 245 | self.generateBanList(counters, queue) |
michael@0 | 246 | |
michael@0 | 247 | t = self.treeSearchRun(fd, ['-m', '-j']) |
michael@0 | 248 | if (t < bestTime): |
michael@0 | 249 | bestTime = t |
michael@0 | 250 | bestCombos = [queue[:]] |
michael@0 | 251 | print('New best time: {0:f}ms'.format(t)) |
michael@0 | 252 | elif int(t) == int(bestTime): |
michael@0 | 253 | bestCombos.append(queue[:]) |
michael@0 | 254 | |
michael@0 | 255 | i = i + 1 |
michael@0 | 256 | |
michael@0 | 257 | return [bestTime, bestCombos] |
michael@0 | 258 | |
michael@0 | 259 | def internalTreeSearch(self, params): |
michael@0 | 260 | fd = params['fd'] |
michael@0 | 261 | methodTime = params['methodTime'] |
michael@0 | 262 | tracerTime = params['tracerTime'] |
michael@0 | 263 | combinedTime = params['combinedTime'] |
michael@0 | 264 | counters = params['counters'] |
michael@0 | 265 | |
michael@0 | 266 | # Build the initial loop data. |
michael@0 | 267 | # If the method JIT already wins, disable tracing by default. |
michael@0 | 268 | # Otherwise, enable tracing by default. |
michael@0 | 269 | if methodTime < combinedTime: |
michael@0 | 270 | enableLoops = True |
michael@0 | 271 | else: |
michael@0 | 272 | enableLoops = False |
michael@0 | 273 | |
michael@0 | 274 | enableLoops = False |
michael@0 | 275 | |
michael@0 | 276 | uct = UCT(self, combinedTime, enableLoops, counters[:], fd, 50000) |
michael@0 | 277 | return uct.run() |
michael@0 | 278 | |
michael@0 | 279 | def treeSearch(self): |
michael@0 | 280 | fd, counters = self.ppForTreeSearch() |
michael@0 | 281 | |
michael@0 | 282 | os.system("cat " + fd.name + " > /tmp/k.js") |
michael@0 | 283 | |
michael@0 | 284 | if os.path.exists('/tmp/permabans'): |
michael@0 | 285 | os.unlink('/tmp/permabans') |
michael@0 | 286 | methodTime = self.treeSearchRun(fd, ['-m']) |
michael@0 | 287 | tracerTime = self.treeSearchRun(fd, ['-j']) |
michael@0 | 288 | combinedTime = self.treeSearchRun(fd, ['-m', '-j']) |
michael@0 | 289 | |
michael@0 | 290 | #Get a rough estimate of how long this benchmark will take to fully compute. |
michael@0 | 291 | upperBound = max(methodTime, tracerTime, combinedTime) |
michael@0 | 292 | upperBound *= 2 ** len(counters) |
michael@0 | 293 | upperBound *= 5 # Number of runs |
michael@0 | 294 | treeSearch = False |
michael@0 | 295 | if (upperBound < 1000): |
michael@0 | 296 | print('Estimating {0:d}ms to test, so picking exhaustive '.format(int(upperBound)) + |
michael@0 | 297 | 'search.') |
michael@0 | 298 | else: |
michael@0 | 299 | upperBound = int(upperBound / 1000) |
michael@0 | 300 | delta = datetime.timedelta(seconds = upperBound) |
michael@0 | 301 | if upperBound < 180: |
michael@0 | 302 | print('Estimating {0:d}s to test, so picking exhaustive '.format(int(upperBound))) |
michael@0 | 303 | else: |
michael@0 | 304 | print('Estimating {0:s} to test, so picking tree search '.format(str(delta))) |
michael@0 | 305 | treeSearch = True |
michael@0 | 306 | |
michael@0 | 307 | best = min(methodTime, tracerTime, combinedTime) |
michael@0 | 308 | |
michael@0 | 309 | params = { |
michael@0 | 310 | 'fd': fd, |
michael@0 | 311 | 'counters': counters, |
michael@0 | 312 | 'methodTime': methodTime, |
michael@0 | 313 | 'tracerTime': tracerTime, |
michael@0 | 314 | 'combinedTime': combinedTime |
michael@0 | 315 | } |
michael@0 | 316 | |
michael@0 | 317 | print('Method JIT: {0:d}ms'.format(int(methodTime))) |
michael@0 | 318 | print('Tracing JIT: {0:d}ms'.format(int(tracerTime))) |
michael@0 | 319 | print('Combined: {0:d}ms'.format(int(combinedTime))) |
michael@0 | 320 | |
michael@0 | 321 | if 1 and treeSearch: |
michael@0 | 322 | results = self.internalTreeSearch(params) |
michael@0 | 323 | else: |
michael@0 | 324 | results = self.internalExhaustiveSearch(params) |
michael@0 | 325 | |
michael@0 | 326 | bestTime = results[0] |
michael@0 | 327 | bestCombos = results[1] |
michael@0 | 328 | print('Search found winning time {0:d}ms!'.format(int(bestTime))) |
michael@0 | 329 | print('Combos at this time: {0:d}'.format(len(bestCombos))) |
michael@0 | 330 | |
michael@0 | 331 | #Find loops that traced every single time |
michael@0 | 332 | for i in range(0, len(counters)): |
michael@0 | 333 | start = counters[i][0] |
michael@0 | 334 | end = counters[i][1] |
michael@0 | 335 | n = len(bestCombos) |
michael@0 | 336 | for j in bestCombos: |
michael@0 | 337 | n -= j[i] |
michael@0 | 338 | print('\tloop @ {0:d}-{1:d} traced {2:d}% of the time'.format( |
michael@0 | 339 | start, end, int(n / len(bestCombos) * 100))) |
michael@0 | 340 | |
michael@0 | 341 | def ppForTreeSearch(self): |
michael@0 | 342 | def onBegin(lines, lineno): |
michael@0 | 343 | lines.append('GLOBAL_THINGY = 1;\n') |
michael@0 | 344 | def onEnd(lines, old, lineno): |
michael@0 | 345 | lines.append('GLOBAL_THINGY = 1;\n') |
michael@0 | 346 | |
michael@0 | 347 | lines = ['var JINT_START_TIME = Date.now();\n', |
michael@0 | 348 | 'var GLOBAL_THINGY = 0;\n'] |
michael@0 | 349 | |
michael@0 | 350 | lines, counters = self.preprocess(lines, onBegin, onEnd) |
michael@0 | 351 | fd = tempfile.NamedTemporaryFile('wt') |
michael@0 | 352 | for line in lines: |
michael@0 | 353 | fd.write(line) |
michael@0 | 354 | fd.write('print(Date.now() - JINT_START_TIME);\n') |
michael@0 | 355 | fd.flush() |
michael@0 | 356 | return [fd, counters] |
michael@0 | 357 | |
michael@0 | 358 | def preprocessForLoopCounting(self): |
michael@0 | 359 | def onBegin(lines, lineno): |
michael@0 | 360 | lines.append('JINT_TRACKER.line_' + str(lineno) + '_start = Date.now();\n') |
michael@0 | 361 | |
michael@0 | 362 | def onEnd(lines, old, lineno): |
michael@0 | 363 | lines.append('JINT_TRACKER.line_' + str(old) + '_end = Date.now();\n') |
michael@0 | 364 | lines.append('JINT_TRACKER.line_' + str(old) + '_total += ' + \ |
michael@0 | 365 | 'JINT_TRACKER.line_' + str(old) + '_end - ' + \ |
michael@0 | 366 | 'JINT_TRACKER.line_' + str(old) + '_start;\n') |
michael@0 | 367 | |
michael@0 | 368 | lines, counters = self.preprocess(onBegin, onEnd) |
michael@0 | 369 | fd = tempfile.NamedTemporaryFile('wt') |
michael@0 | 370 | fd.write('var JINT_TRACKER = { };\n') |
michael@0 | 371 | for c in counters: |
michael@0 | 372 | fd.write('JINT_TRACKER.line_' + str(c) + '_start = 0;\n') |
michael@0 | 373 | fd.write('JINT_TRACKER.line_' + str(c) + '_end = 0;\n') |
michael@0 | 374 | fd.write('JINT_TRACKER.line_' + str(c) + '_total = 0;\n') |
michael@0 | 375 | fd.write('JINT_TRACKER.begin = Date.now();\n') |
michael@0 | 376 | for line in lines: |
michael@0 | 377 | fd.write(line) |
michael@0 | 378 | fd.write('JINT_TRACKER.total = Date.now() - JINT_TRACKER.begin;\n') |
michael@0 | 379 | for c in self.counters: |
michael@0 | 380 | fd.write('print("line ' + str(c) + ': " + JINT_TRACKER.line_' + str(c) + |
michael@0 | 381 | '_total);') |
michael@0 | 382 | fd.write('print("total: " + JINT_TRACKER.total);') |
michael@0 | 383 | fd.flush() |
michael@0 | 384 | return fd |
michael@0 | 385 | |
michael@0 | 386 | if __name__ == '__main__': |
michael@0 | 387 | script_path = os.path.abspath(__file__) |
michael@0 | 388 | script_dir = os.path.dirname(script_path) |
michael@0 | 389 | test_dir = os.path.join(script_dir, 'tests') |
michael@0 | 390 | lib_dir = os.path.join(script_dir, 'lib') |
michael@0 | 391 | |
michael@0 | 392 | # The [TESTS] optional arguments are paths of test files relative |
michael@0 | 393 | # to the jit-test/tests directory. |
michael@0 | 394 | |
michael@0 | 395 | from optparse import OptionParser |
michael@0 | 396 | op = OptionParser(usage='%prog [options] JS_SHELL test') |
michael@0 | 397 | (OPTIONS, args) = op.parse_args() |
michael@0 | 398 | if len(args) < 2: |
michael@0 | 399 | op.error('missing JS_SHELL and test argument') |
michael@0 | 400 | # We need to make sure we are using backslashes on Windows. |
michael@0 | 401 | JS = realpath(args[0]) |
michael@0 | 402 | test = realpath(args[1]) |
michael@0 | 403 | |
michael@0 | 404 | bm = Benchmark(JS, test) |
michael@0 | 405 | bm.treeSearch() |
michael@0 | 406 | # bm.preprocess() |
michael@0 | 407 | # bm.run('mjit', ['-m']) |
michael@0 | 408 | # bm.run('tjit', ['-j']) |
michael@0 | 409 | # bm.run('m+tjit', ['-m', '-j']) |
michael@0 | 410 | # bm.chart() |
michael@0 | 411 |