js/src/jit-test/etc/generate-lookupswitch-tests.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1
michael@0 2 /**
michael@0 3 * A test case spec is an array of objects of the following kind:
michael@0 4 * { 'match': Num|Str|Null,
michael@0 5 * 'body': Num|Str|Null,
michael@0 6 * 'fallthrough': Boolean }
michael@0 7 *
michael@0 8 * If the 'match' is null, then it represents a 'default:'
michael@0 9 * If the 'match' is not null, it represents a 'case X:' where X is the value.
michael@0 10 * If the 'body' is null, then it means that the case body is empty. Otherwise,
michael@0 11 * it means that the case body is a single 'arr.push(V);' where "arr" is an input
michael@0 12 * array to the function containing the switch statement, and V is the value.
michael@0 13 * If 'fallthrough' is false, then the body is terminated with a break, otherwise
michael@0 14 * it is not.
michael@0 15 *
michael@0 16 * So a spec: [{'match':3, 'body':null, 'fallthrough':false}, {'match':null, 'body':"foo", 'fallthrough':true}]
michael@0 17 * Represents a switch function:
michael@0 18 * function(x, arr) {
michael@0 19 * switch(x) {
michael@0 20 * case 3:
michael@0 21 * break;
michael@0 22 * default:
michael@0 23 * arr.push('foo');
michael@0 24 * }
michael@0 25 * }
michael@0 26 *
michael@0 27 * GenerateSpecPermutes generates a bunch of relevant specs, using the given case match-values,
michael@0 28 * and appends them to result the array passed into it.
michael@0 29 *
michael@0 30 * InterpretSwitch takes a spec, a value, and a result array, and behaves as if the switch specified
michael@0 31 * by the spec had been called on the value and the result array.
michael@0 32 *
michael@0 33 * VerifySwitchSpec is there but not used in the code. I was using it while testing the test case
michael@0 34 * generator. It verifies that a switch spec is sane.
michael@0 35 *
michael@0 36 * RunSpec uses eval to run the test directly. It's not used currently.
michael@0 37 *
michael@0 38 * GenerateSwitchCode generates a string of the form "function NAME(x, arg) { .... }" which
michael@0 39 * contains the switch modeled by its input spec.
michael@0 40 *
michael@0 41 * RunTest is there to be used from within the generated script. Its code is dumped out
michael@0 42 * to the generated script text, and invoked there.
michael@0 43 *
michael@0 44 * Hope all of this makes some sort of sense.
michael@0 45 * -kannan
michael@0 46 */
michael@0 47
michael@0 48 /** HELPERS **/
michael@0 49
michael@0 50 function ASSERT(cond, msg) { assertEq(cond, true, msg); }
michael@0 51
michael@0 52 function IsUndef(x) { return typeof(x) == 'undefined'; }
michael@0 53 function IsNull(x) { return typeof(x) == 'object' && x == null; }
michael@0 54 function IsNum(x) { return typeof(x) == 'number'; }
michael@0 55 function IsStr(x) { return typeof(x) == 'string'; }
michael@0 56 function IsBool(x) { return typeof(x) == 'boolean'; }
michael@0 57
michael@0 58 function Repr(x) {
michael@0 59 ASSERT(IsNum(x) || IsStr(x), "Repr");
michael@0 60 if(IsNum(x)) { return ""+x; }
michael@0 61 else { return "'"+x+"'"; }
michael@0 62 }
michael@0 63
michael@0 64 function RandBool() { return Math.random() >= 0.5; }
michael@0 65 function RandInt(max) {
michael@0 66 if(IsUndef(max)) { max = 0x7fffffff; }
michael@0 67 return (Math.random() * max)|0;
michael@0 68 }
michael@0 69
michael@0 70 var CHARS = "abcdefghijklmnopqrstuvywxyzABCDEFGHIJKLMNOPQRSTUVYWXYZ0123456789~!@#$%^&*()-_=+{}[]";
michael@0 71 function RandStr() {
michael@0 72 var arr = [];
michael@0 73 var len = Math.floor(Math.random() * 10) + 1;
michael@0 74 for(var i = 0; i < len; i++) {
michael@0 75 var c = Math.floor(Math.random() * CHARS.length);
michael@0 76 arr.push(CHARS[c]);
michael@0 77 }
michael@0 78 return arr.join('');
michael@0 79 }
michael@0 80
michael@0 81 function RandVal() { return RandBool() ? RandInt() : RandStr(); }
michael@0 82
michael@0 83 /**
michael@0 84 * Compare two arrays and ensure they are equal.
michael@0 85 */
michael@0 86 function ArraysEqual(arr1, arr2) {
michael@0 87 ASSERT(arr1.length == arr2.length, "Lengths not equal");
michael@0 88 for(var i = 0; i < arr1.length; i++) {
michael@0 89 ASSERT(typeof(arr1[i]) == typeof(arr2[i]), "Types not equal for position " + i);
michael@0 90 ASSERT(arr1[i] == arr2[i], "Values not equal for position " + i);
michael@0 91 }
michael@0 92 }
michael@0 93
michael@0 94 function VerifySwitchSpec(spec) {
michael@0 95 var foundDefault = undefined;
michael@0 96 for(var i = 0; i < spec.length; i++) {
michael@0 97 var caseSpec = spec[i],
michael@0 98 match = caseSpec.match,
michael@0 99 body = caseSpec.body,
michael@0 100 fallthrough = caseSpec.fallthrough;
michael@0 101 ASSERT(IsNum(match) || IsStr(match) || IsNull(match), "Invalid case match for " + i);
michael@0 102 ASSERT(IsNum(body) || IsStr(body) || IsNull(body), "Invalid case body for " + i);
michael@0 103 ASSERT(IsBool(fallthrough), "Invalid fallthrough for " + i);
michael@0 104
michael@0 105 if(IsNull(match)) {
michael@0 106 ASSERT(IsUndef(foundDefault), "Duplicate default found at " + i);
michael@0 107 foundDefault = i;
michael@0 108 }
michael@0 109 }
michael@0 110 }
michael@0 111
michael@0 112 /**
michael@0 113 * Do a manual interpretation of a particular spec, given an input
michael@0 114 * and outputting to an output array.
michael@0 115 */
michael@0 116 function InterpretSwitch(spec, input, outputArray) {
michael@0 117 var foundMatch = undefined, foundDefault = undefined;
michael@0 118 // Go through cases, trying to find a matching clause.
michael@0 119 for(var i = 0; i < spec.length; i++) {
michael@0 120 var caseSpec = spec[i], match = caseSpec.match;
michael@0 121
michael@0 122 if(IsNull(match)) {
michael@0 123 foundDefault = i;
michael@0 124 continue;
michael@0 125 } else if(match === input) {
michael@0 126 foundMatch = i;
michael@0 127 break;
michael@0 128 }
michael@0 129 }
michael@0 130 // Select either matching clause or default.
michael@0 131 var matchI = IsNum(foundMatch) ? foundMatch : foundDefault;
michael@0 132
michael@0 133 // If match or default was found, interpret body from that point on.
michael@0 134 if(IsNum(matchI)) {
michael@0 135 for(var i = matchI; i < spec.length; i++) {
michael@0 136 var caseSpec = spec[i],
michael@0 137 match = caseSpec.match,
michael@0 138 body = caseSpec.body,
michael@0 139 fallthrough = caseSpec.fallthrough;
michael@0 140 if(!IsNull(body)) { outputArray.push(body); }
michael@0 141 if(!fallthrough) { break; }
michael@0 142 }
michael@0 143 }
michael@0 144 }
michael@0 145
michael@0 146 /**
michael@0 147 * Generate the code string for a javascript function containing the
michael@0 148 * switch specified by the spec, in pure JS syntax.
michael@0 149 */
michael@0 150 function GenerateSwitchCode(spec, name) {
michael@0 151 var lines = [];
michael@0 152 if(!name) { name = ""; }
michael@0 153
michael@0 154 lines.push("function "+name+"(x, arr) {");
michael@0 155 lines.push(" switch(x) {");
michael@0 156 for(var i = 0; i < spec.length; i++) {
michael@0 157 var caseSpec = spec[i],
michael@0 158 match = caseSpec.match,
michael@0 159 body = caseSpec.body,
michael@0 160 fallthrough = caseSpec.fallthrough;
michael@0 161
michael@0 162 if(IsNull(match)) { lines.push(" default:"); }
michael@0 163 else { lines.push(" case "+Repr(match)+":"); }
michael@0 164
michael@0 165 if(!IsNull(body)) { lines.push(" arr.push("+Repr(body)+");"); }
michael@0 166 if(!fallthrough) { lines.push(" break;"); }
michael@0 167 }
michael@0 168 lines.push(" }");
michael@0 169 lines.push("}");
michael@0 170 return lines.join("\n");
michael@0 171 }
michael@0 172
michael@0 173 /**
michael@0 174 * Generates all possible specs for a given set of case match values.
michael@0 175 */
michael@0 176 function GenerateSpecPermutes(matchVals, resultArray) {
michael@0 177 ASSERT((0 < matchVals.length) && (matchVals.length <= 5), "Invalid matchVals");
michael@0 178 var maxPermuteBody = (1 << matchVals.length) - 1;
michael@0 179 for(var bod_pm = 0; bod_pm <= maxPermuteBody; bod_pm++) {
michael@0 180 var maxPermuteFallthrough = (1 << matchVals.length) - 1;
michael@0 181
michael@0 182 for(var ft_pm = 0; ft_pm <= maxPermuteFallthrough; ft_pm++) {
michael@0 183 // use bod_m and ft_pm to decide the placement of null vs value bodies,
michael@0 184 // and the placement of fallthroughs vs breaks.
michael@0 185 // Empty bodies always fall through, so fallthrough bitmask 1s must be
michael@0 186 // a subset of the body bitmask 1s.
michael@0 187 if((bod_pm | ft_pm) != bod_pm) {
michael@0 188 continue;
michael@0 189 }
michael@0 190
michael@0 191 var spec = [];
michael@0 192 for(var k = 0; k < matchVals.length; k++) {
michael@0 193 var match = matchVals[k];
michael@0 194 var body = ((bod_pm & (1 << k)) > 0) ? null : RandVal();
michael@0 195 var fallthrough = ((ft_pm & (1 << k)) > 0) ? true : false;
michael@0 196 var caseSpec = {'match':match, 'body':body, 'fallthrough':fallthrough};
michael@0 197 spec.push(caseSpec);
michael@0 198 }
michael@0 199
michael@0 200 // Variant specs for following cases:
michael@0 201
michael@0 202 // Default with empty body, fallthrough
michael@0 203 GenerateDefaultPermutes(spec, null, true, resultArray);
michael@0 204 // Default with nonempty body, fallthrough
michael@0 205 GenerateDefaultPermutes(spec, RandVal(), true, resultArray);
michael@0 206 // Default with nonempty body, break
michael@0 207 GenerateDefaultPermutes(spec, RandVal(), false, resultArray);
michael@0 208 }
michael@0 209 }
michael@0 210 }
michael@0 211 function GenerateDefaultPermutes(spec, body, fallthrough, resultArray) {
michael@0 212 if(spec.length <= 2) {
michael@0 213 for(var i = 0; i <= spec.length; i++) {
michael@0 214 var copy = CopySpec(spec);
michael@0 215 if(IsNull(body)) {
michael@0 216 copy.splice(i,0,{'match':null, 'body':null, 'fallthrough':true});
michael@0 217 } else {
michael@0 218 copy.splice(i,0,{'match':null, 'body':body, 'fallthrough':fallthrough});
michael@0 219 }
michael@0 220 resultArray.push(copy);
michael@0 221 }
michael@0 222 } else {
michael@0 223 var posns = [0, Math.floor(spec.length / 2), spec.length];
michael@0 224 posns.forEach(function (i) {
michael@0 225 var copy = CopySpec(spec);
michael@0 226 if(IsNull(body)) {
michael@0 227 copy.splice(i,0,{'match':null, 'body':null, 'fallthrough':true});
michael@0 228 } else {
michael@0 229 copy.splice(i,0,{'match':null, 'body':body, 'fallthrough':fallthrough});
michael@0 230 }
michael@0 231 resultArray.push(copy);
michael@0 232 });
michael@0 233 }
michael@0 234 }
michael@0 235 function CopySpec(spec) {
michael@0 236 var newSpec = [];
michael@0 237 for(var i = 0; i < spec.length; i++) {
michael@0 238 var caseSpec = spec[i];
michael@0 239 newSpec.push({'match':caseSpec.match,
michael@0 240 'body':caseSpec.body,
michael@0 241 'fallthrough':caseSpec.fallthrough});
michael@0 242 }
michael@0 243 return newSpec;
michael@0 244 }
michael@0 245
michael@0 246
michael@0 247 function RunSpec(spec, matchVals) {
michael@0 248 var code = GenerateSwitchCode(spec);
michael@0 249
michael@0 250 // Generate roughly 200 inputs for the test case spec, exercising
michael@0 251 // every match value, as well as 3 randomly generated values for every
michael@0 252 // iteration of the match values.
michael@0 253 var inputs = [];
michael@0 254 while(inputs.length < 500) {
michael@0 255 for(var i = 0; i < matchVals.length; i++) { inputs.push(matchVals[i]); }
michael@0 256 for(var i = 0; i < 3; i++) { inputs.push(RandVal()); }
michael@0 257 }
michael@0 258
michael@0 259 // Interpret the lookupswitch with the inputs.
michael@0 260 var interpResults = [];
michael@0 261 for(var i = 0; i < inputs.length; i++) {
michael@0 262 InterpretSwitch(spec, inputs[i], interpResults);
michael@0 263 }
michael@0 264
michael@0 265 // Run compiled lookupswitch with the inputs.
michael@0 266 var fn = eval("_ = " + code);
michael@0 267 print("Running spec: " + code);
michael@0 268 var compileResults = RunCompiled(fn, inputs);
michael@0 269 print("Done Running spec");
michael@0 270
michael@0 271 // Verify that they produce the same output.
michael@0 272 ASSERT(interpResults.length == compileResults.length, "Length mismatch");
michael@0 273 for(var i = 0; i < interpResults.length; i++) {
michael@0 274 ASSERT(interpResults[i] == compileResults[i], "Value mismatch");
michael@0 275 }
michael@0 276 }
michael@0 277 function RunCompiled(fn, inputs) {
michael@0 278 var results = [];
michael@0 279 var len = inputs.length;
michael@0 280 for(var i = 0; i < len; i++) { fn(inputs[i], results); }
michael@0 281 return results;
michael@0 282 }
michael@0 283
michael@0 284 function PrintSpec(spec, inputs, fname) {
michael@0 285 var code = GenerateSwitchCode(spec, fname);
michael@0 286 var input_s = fname + ".INPUTS = [" + inputs.map(Repr).join(', ') + "];";
michael@0 287 var spec_s = fname + ".SPEC = " + JSON.stringify(spec) + ";";
michael@0 288 print(code + "\n" + input_s + "\n" + spec_s);
michael@0 289 }
michael@0 290
michael@0 291 function RunTest(test) {
michael@0 292 // Exercise every test case as well as one case which won't match with any of the
michael@0 293 // ("But what if it randomly generates a string case match whose value is
michael@0 294 // UNMATCHED_CASE?!", you ask incredulously. Well, RandStr limits itself to 11 chars.
michael@0 295 // So there.)
michael@0 296 var inputs = test.INPUTS;
michael@0 297 inputs.push("UNMATCHED_CASE");
michael@0 298 var spec = test.SPEC;
michael@0 299
michael@0 300 var results1 = [];
michael@0 301 for(var i = 0; i < 80; i++) {
michael@0 302 for(var j = 0; j < inputs.length; j++) {
michael@0 303 test(inputs[j], results1);
michael@0 304 }
michael@0 305 }
michael@0 306
michael@0 307 var results2 = [];
michael@0 308 for(var i = 0; i < 80; i++) {
michael@0 309 for(var j = 0; j < inputs.length; j++) {
michael@0 310 InterpretSwitch(spec, inputs[j], results2);
michael@0 311 }
michael@0 312 }
michael@0 313 ArraysEqual(results1, results2);
michael@0 314 }
michael@0 315
michael@0 316 // NOTES:
michael@0 317 // * RunTest is used within the generated test script.
michael@0 318 // * InterpretSwitch is printed out into the generated test script.
michael@0 319
michael@0 320 print("/////////////////////////////////////////");
michael@0 321 print("// This is a generated file!");
michael@0 322 print("// See jit-tests/etc/generate-lookupswitch-tests.js for the code");
michael@0 323 print("// that generated this code!");
michael@0 324 print("/////////////////////////////////////////");
michael@0 325 print("");
michael@0 326 print("/////////////////////////////////////////");
michael@0 327 print("// PRELUDE //");
michael@0 328 print("/////////////////////////////////////////");
michael@0 329 print("");
michael@0 330 print("// Avoid eager compilation of the global-scope.");
michael@0 331 print("try{} catch (x) {};");
michael@0 332 print("");
michael@0 333 print(ASSERT);
michael@0 334 print(IsNull);
michael@0 335 print(IsNum);
michael@0 336 print(ArraysEqual);
michael@0 337 print(InterpretSwitch);
michael@0 338 print(RunTest);
michael@0 339 print("");
michael@0 340 print("/////////////////////////////////////////");
michael@0 341 print("// TEST CASES //");
michael@0 342 print("/////////////////////////////////////////");
michael@0 343 print("");
michael@0 344 print("var TESTS = [];");
michael@0 345 var MATCH_SETS = [["foo", "bar", "zing"]];
michael@0 346 var count = 0;
michael@0 347 for(var i = 0; i < MATCH_SETS.length; i++) {
michael@0 348 var matchSet = MATCH_SETS[i];
michael@0 349 var specs = [];
michael@0 350 GenerateSpecPermutes(matchSet, specs);
michael@0 351 for(var j = 0; j < specs.length; j++) {
michael@0 352 count++;
michael@0 353 PrintSpec(specs[j], matchSet.slice(), 'test_'+count);
michael@0 354 print("TESTS.push(test_"+count+");\n");
michael@0 355 }
michael@0 356 }
michael@0 357
michael@0 358 print("");
michael@0 359 print("/////////////////////////////////////////");
michael@0 360 print("// RUNNER //");
michael@0 361 print("/////////////////////////////////////////");
michael@0 362 print("");
michael@0 363 print("for(var i = 0; i < TESTS.length; i++) {");
michael@0 364 print(" RunTest(TESTS[i]);");
michael@0 365 print("}");

mercurial