|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 // Explanation of minItemsTestingThreshold: |
|
6 // |
|
7 // If the volume of input items in a test is small, then all of them |
|
8 // may be processed during warmup alone, and the parallel-invocation |
|
9 // will trivially succeed even if we are intentionally trying to |
|
10 // detect a failure. |
|
11 // |
|
12 // The maximum number of items processed by sequential warmups for |
|
13 // ArrayBuildPar is: |
|
14 // maxSeqItems = maxBailouts * numSlices * CHUNK_SIZE |
|
15 // |
|
16 // For maxBailouts = 3, maxSeqItems == 3 * 8 * 32 == 768 |
|
17 // For maxBailouts = 5, maxSeqItems == 5 * 8 * 32 == 1280 |
|
18 // |
|
19 // Our test code does not have access to the values of these constants |
|
20 // (maxBailouts, numSlices, CHUNK_SIZE). Therefore, the value of |
|
21 // minItemsTestingThreshold should be kept in sync with some value |
|
22 // greater than maxSeqItems as calculated above. |
|
23 // |
|
24 // This is still imperfect since it assumes numSlices <= 8, but |
|
25 // numSlices is machine-dependent. |
|
26 // (TODO: consider exposing numSlices via builtin/TestingFunctions.cpp) |
|
27 |
|
28 var minItemsTestingThreshold = 1024; |
|
29 |
|
30 // The standard sequence of modes to test. |
|
31 // First mode compiles for parallel exec. |
|
32 // Second mode checks that parallel exec does not bail. |
|
33 // Final mode tests the sequential fallback path. |
|
34 var MODE_STRINGS = ["compile", "par", "seq"]; |
|
35 var MODES = MODE_STRINGS.map(s => ({mode: s})); |
|
36 |
|
37 var INVALIDATE_MODE_STRINGS = ["seq", "compile", "par", "seq"]; |
|
38 var INVALIDATE_MODES = INVALIDATE_MODE_STRINGS.map(s => ({mode: s})); |
|
39 |
|
40 function build(n, f) { |
|
41 var result = []; |
|
42 for (var i = 0; i < n; i++) |
|
43 result.push(f(i)); |
|
44 return result; |
|
45 } |
|
46 |
|
47 function range(n, m) { |
|
48 // Returns an array with [n..m] (include on n, exclusive on m) |
|
49 |
|
50 var result = []; |
|
51 for (var i = n; i < m; i++) |
|
52 result.push(i); |
|
53 return result; |
|
54 } |
|
55 |
|
56 function seq_scan(array, f) { |
|
57 // Simple sequential version of scan() that operates over an array |
|
58 |
|
59 var result = []; |
|
60 result[0] = array[0]; |
|
61 for (var i = 1; i < array.length; i++) { |
|
62 result[i] = f(result[i-1], array[i]); |
|
63 } |
|
64 return result; |
|
65 } |
|
66 |
|
67 function assertAlmostEq(v1, v2) { |
|
68 if (v1 === v2) |
|
69 return true; |
|
70 // + and other fp ops can vary somewhat when run in parallel! |
|
71 assertEq(typeof v1, "number"); |
|
72 assertEq(typeof v2, "number"); |
|
73 var diff = Math.abs(v1 - v2); |
|
74 var percent = diff / v1 * 100.0; |
|
75 print("v1 = " + v1); |
|
76 print("v2 = " + v2); |
|
77 print("% diff = " + percent); |
|
78 assertEq(percent < 1e-10, true); // off by an less than 1e-10%...good enough. |
|
79 } |
|
80 |
|
81 function assertStructuralEq(e1, e2) { |
|
82 if (e1 instanceof Array && e2 instanceof Array) { |
|
83 assertEqArray(e1, e2); |
|
84 } else if (e1 instanceof Object && e2 instanceof Object) { |
|
85 assertEq(e1.__proto__, e2.__proto__); |
|
86 for (prop in e1) { |
|
87 if (e1.hasOwnProperty(prop)) { |
|
88 assertEq(e2.hasOwnProperty(prop), true); |
|
89 assertStructuralEq(e1[prop], e2[prop]); |
|
90 } |
|
91 } |
|
92 } else { |
|
93 assertEq(e1, e2); |
|
94 } |
|
95 } |
|
96 |
|
97 function assertEqArray(a, b) { |
|
98 assertEq(a.length, b.length); |
|
99 for (var i = 0, l = a.length; i < l; i++) { |
|
100 try { |
|
101 assertStructuralEq(a[i], b[i]); |
|
102 } catch (e) { |
|
103 print("...in index", i, "of", l); |
|
104 throw e; |
|
105 } |
|
106 } |
|
107 } |
|
108 |
|
109 // Checks that whenever we execute this in parallel mode, |
|
110 // it bails out. `opFunction` should be a closure that takes a |
|
111 // mode parameter and performs some parallel array operation. |
|
112 // This closure will be invoked repeatedly. |
|
113 // |
|
114 // Here is an example of the expected usage: |
|
115 // |
|
116 // assertParallelExecWillBail(function(m) { |
|
117 // Array.buildPar(..., m) |
|
118 // }); |
|
119 // |
|
120 // where the `Array.buildPar(...)` is a stand-in |
|
121 // for some parallel array operation. |
|
122 function assertParallelExecWillBail(opFunction) { |
|
123 opFunction({mode:"compile"}); // get the script compiled |
|
124 opFunction({mode:"bailout"}); // check that it bails when executed |
|
125 } |
|
126 |
|
127 // Checks that when we execute this in parallel mode, |
|
128 // some bailouts will occur but we will recover and |
|
129 // return to parallel execution mode. `opFunction` is a closure |
|
130 // that expects a mode, just as in `assertParallelExecWillBail`. |
|
131 function assertParallelExecWillRecover(opFunction) { |
|
132 opFunction({mode:"compile"}); // get the script compiled |
|
133 opFunction({mode:"recover"}); // check that it bails when executed |
|
134 } |
|
135 |
|
136 // Checks that we will (eventually) be able to compile and exection |
|
137 // `opFunction` in parallel mode. Invokes `cmpFunction` with the |
|
138 // result. For some tests, it takes many compile rounds to reach a TI |
|
139 // fixed point. So this function will repeatedly attempt to invoke |
|
140 // `opFunction` with `compile` and then `par` mode until getting a |
|
141 // successful `par` run. After enough tries, of course, we give up |
|
142 // and declare a test failure. |
|
143 function assertParallelExecSucceeds(opFunction, cmpFunction) { |
|
144 var failures = 0; |
|
145 while (true) { |
|
146 print("Attempting compile #", failures); |
|
147 var result = opFunction({mode:"compile"}); |
|
148 cmpFunction(result); |
|
149 |
|
150 try { |
|
151 print("Attempting parallel run #", failures); |
|
152 var result = opFunction({mode:"par"}); |
|
153 cmpFunction(result); |
|
154 break; |
|
155 } catch (e) { |
|
156 failures++; |
|
157 if (failures > 5) { |
|
158 throw e; // doesn't seem to be reaching a fixed point! |
|
159 } else { |
|
160 print(e); |
|
161 } |
|
162 } |
|
163 } |
|
164 |
|
165 print("Attempting sequential run"); |
|
166 var result = opFunction({mode:"seq"}); |
|
167 cmpFunction(result); |
|
168 } |
|
169 |
|
170 // Compares an Array constructed in parallel against one constructed |
|
171 // sequentially. `func` should be the closure to provide as argument. For |
|
172 // example: |
|
173 // |
|
174 // assertArraySeqParResultsEq([1, 2, 3], "map", i => i + 1) |
|
175 // |
|
176 // would check that `[1, 2, 3].map(i => i+1)` and `[1, 2, 3].mapPar(i => i+1)` |
|
177 // yield the same result. |
|
178 // |
|
179 // Based on `assertParallelExecSucceeds` |
|
180 function assertArraySeqParResultsEq(arr, op, func, cmpFunc) { |
|
181 if (!cmpFunc) |
|
182 cmpFunc = assertStructuralEq; |
|
183 var expected = arr[op].apply(arr, [func]); |
|
184 assertParallelExecSucceeds( |
|
185 function (m) { return arr[op + "Par"].apply(arr, [func, m]); }, |
|
186 function (r) { cmpFunc(expected, r); }); |
|
187 } |
|
188 |
|
189 // Similar to `compareAgainstArray`, but for the `scan` method which |
|
190 // does not appear on array. |
|
191 function testArrayScanPar(jsarray, func, cmpFunction) { |
|
192 if (!cmpFunction) |
|
193 cmpFunction = assertStructuralEq; |
|
194 var expected = seq_scan(jsarray, func); |
|
195 |
|
196 // Unfortunately, it sometimes happens that running 'par' twice in a |
|
197 // row causes bailouts and other unfortunate things! |
|
198 |
|
199 assertParallelExecSucceeds( |
|
200 function(m) { |
|
201 print(m.mode + " " + m.expect); |
|
202 var p = jsarray.scanPar(func, m); |
|
203 return p; |
|
204 }, |
|
205 function(r) { |
|
206 cmpFunction(expected, r); |
|
207 }); |
|
208 } |
|
209 |
|
210 // Checks that `opFunction`, when run with each of the modes |
|
211 // in `modes`, returns the same value each time. |
|
212 function assertParallelModesCommute(modes, opFunction) { |
|
213 var expected = undefined; |
|
214 var acc = opFunction(modes[0]); |
|
215 assertParallelExecSucceeds( |
|
216 opFunction, |
|
217 function(r) { |
|
218 if (expected === undefined) |
|
219 expected = r; |
|
220 else |
|
221 assertStructuralEq(expected, r); |
|
222 }); |
|
223 } |