adhoc-multiplatform-test.js (10207B)
1 // This file provides a version of the functions 2 // 3 // codegenTestX64_adhoc (src/jit-test/lib/codegen-x64-test.js) 4 // codegenTestX86_adhoc (src/jit-test/lib/codegen-x86-test.js) 5 // codegenTestARM64_adhoc (src/jit-test/lib/codegen-arm64-test.js) 6 // (and the equivalent arm(32) function) 7 // 8 // generalised so the output can be specified for all 4 targets in one place. 9 // 10 // Usage: 11 // codegenTestMultiplatform_adhoc(module_text, export_name, 12 // expectedAllTargets, options = {}) 13 // 14 // where 15 // `expectedAllTargets` states the expected-output regexps for each target, 16 // thusly: 17 // 18 // {x64: 'text', x86: 'text', arm64: 'text', arm: 'text'} 19 // 20 // The arm(32) expected output is optional. The other 3 must be present. 21 // 22 // Each 'text' is a string that represents a regular expression, possibly 23 // with newlines, representing the instruction or instructions we are looking 24 // for for the operator. Spaces in the expected-pattern are arbitrary, we 25 // preprocess the pattern to replace any space string with \s+. Lines are 26 // separated by newlines and leading and trailing spaces are stripped. 27 // Pattern strings may be empty, denoting "no instruction(s)". 28 // 29 // options specifies options thusly: 30 // 31 // instanceBox: if present, an object with a `value` property that will 32 // receive the constructed instance 33 // 34 // log: for debugging -- print the disassembly and other info helpful to 35 // resolving test failures. This is also printed on a test failure 36 // regardless of the log setting. 37 // 38 // features: this is passed on verbatim to wasmEvalText, 39 // as its third argument. 40 // 41 // no_prefix: by default, the required pattern must be immediately preceded 42 // by `<target>_prefix`, and this is checked. Setting this to 43 // true skips the check. Try not to use this. 44 // 45 // no_suffix: by default, the required pattern must be immediately followed 46 // by `<target>_suffix`, and this is checked. Setting this to 47 // true skips the check. Try not to use this. 48 // 49 // no_prefix/no_suffix apply to all 4 targets. Per-target overrides are 50 // supported, by putting them in a suitably tagged sub-object, eg: 51 // options = {x86: {no_prefix: true}} 52 53 load(libdir + "codegen-test-common.js"); 54 55 // Architectures supported by this script. 56 const knownArchs = ["x64", "x86", "arm64", "arm"]; 57 58 // Architectures for which `expectedAllTargets` must supply an expected result. 59 const requiredArchs = ["x64", "x86", "arm64"]; 60 61 // These define the end-of-prologue ("prefix") and start-of-epilogue 62 // ("suffix") to be matched. 63 const archOptions = 64 {x64: { 65 encoding: `(?:${HEX}{2} )*`, 66 // The move from r14 to rbp is writing the callee's wasm instance 67 // into the frame for debug checks -- see WasmFrame.h. 68 prefix: `mov %rsp, %rbp( 69 movq %r14, (0x10|0x30)\\(%rbp\\))?`, 70 suffix: `pop %rbp` 71 }, 72 x86: { 73 encoding: `(?:${HEX}{2} )*`, 74 // The move from esi to rbp is writing the callee's wasm instance 75 // into the frame for debug checks -- see WasmFrame.h. The mov to 76 // e[ac]x is debug code, inserted by the register allocator to 77 // clobber e[ac]x before a move group. But it is only present if 78 // there is a move group there. 79 prefix: `mov %esp, %ebp( 80 movl %esi, 0x08\\(%rbp\\))?( 81 mov \\$0xDEADBEEF, %e.x)?`, 82 // `.bp` because zydis chooses `rbp` even on 32-bit systems. 83 suffix: `pop %.bp` 84 }, 85 arm64: { 86 encoding: `${HEX}{8}`, 87 // The move from x23 to x29 is writing the callee's wasm instance 88 // into the frame for debug checks -- see WasmFrame.h. 89 prefix: `mov x29, sp 90 mov x28, sp( 91 str x23, \\[x29, #16\\])?`, 92 suffix: `ldr x30, \\[sp, #8\\] 93 ldr x29, \\[sp\\]` 94 }, 95 arm: { 96 encoding: `${HEX}{8}\\s+${HEX}{8}`, 97 // The move from r9 to fp is writing the callee's wasm instance into 98 // the frame for debug checks -- see WasmFrame.h. 99 prefix: `str fp, \\[sp, #-4\\]! 100 mov fp, sp( 101 str r9, \\[fp, #\\+8\\])?`, 102 suffix: `ldr fp, \\[sp\\], #\\+4` 103 } 104 }; 105 106 // The options object may a mix of generic (all-targets) options and contain 107 // sub-objects containing arch-specific options, for example: 108 // 109 // {a_generic_option: 1337, x86: {no_prefix:true}, arm64: {foo:4771}} 110 // 111 // promoteArchSpecificOptions lifts options for `archName` to the top level 112 // and deletes *all* arch-specific subobjects, hence producing the final 113 // to-be-used option set. For the above example, if `archName` is "x86" we 114 // get: 115 // 116 // {a_generic_option: 1337, no_prefix: true} 117 // 118 function promoteArchSpecificOptions(options, archName) { 119 assertEq(true, knownArchs.some(a => archName == a)); 120 if (options.hasOwnProperty(archName)) { 121 let archOptions = options[archName]; 122 for (optName in archOptions) { 123 options[optName] = archOptions[optName]; 124 if (options.log) { 125 print("---- adding " + archName + "-specific option {" 126 + optName + ":" + archOptions[optName] + "}"); 127 } 128 } 129 } 130 for (a of knownArchs) { 131 delete options[a]; 132 } 133 if (options.log) { 134 print("---- final options"); 135 for (optName in options) { 136 print("{" + optName + ":" + options[optName] + "}"); 137 } 138 } 139 return options; 140 } 141 142 // Main test function. See comments at top of this file. 143 function codegenTestMultiplatform_adhoc(module_text, export_name, 144 expectedAllTargets, options = {}) { 145 assertEq(hasDisassembler(), true); 146 147 // Check that we've been provided with an expected result for at least 148 // x64, x86 and arm64. 149 assertEq(true, 150 requiredArchs.every(a => expectedAllTargets.hasOwnProperty(a))); 151 152 // Poke the build-configuration object to find out what target we're 153 // generating code for. 154 let genX64 = getBuildConfiguration("x64"); 155 let genX86 = getBuildConfiguration("x86"); 156 let genArm64 = getBuildConfiguration("arm64"); 157 let genArm = getBuildConfiguration("arm"); 158 // So far so good, except .. X64 or X86 might be emulating something else. 159 if (genX64 && genArm64 && getBuildConfiguration("arm64-simulator")) { 160 genX64 = false; 161 } 162 if (genX86 && genArm && getBuildConfiguration("arm-simulator")) { 163 genX86 = false; 164 } 165 166 // Check we've definitively identified exactly one architecture to test. 167 assertEq(1, [genX64, genX86, genArm64, genArm].map(x => x ? 1 : 0) 168 .reduce((a,b) => a+b, 0)); 169 170 // Decide on the arch name for which we're testing. Everything is keyed 171 // off this. 172 let archName = ""; 173 if (genX64) { 174 archName = "x64"; 175 } else if (genX86) { 176 archName = "x86"; 177 } else if (genArm64) { 178 archName = "arm64"; 179 } else if (genArm) { 180 archName = "arm"; 181 } 182 if (options.log) { 183 print("---- testing for architecture \"" + archName + "\""); 184 } 185 // If this fails, it means we're running on an "unknown" architecture. 186 assertEq(true, archName.length > 0); 187 188 // Finalise options, by promoting arch-specific ones to the top level of 189 // the options object. 190 options = promoteArchSpecificOptions(options, archName); 191 192 // Get the architecture-specific strings for the target. 193 assertEq(true, archOptions.hasOwnProperty(archName)); 194 let encoding = archOptions[archName].encoding; 195 let prefix = archOptions[archName].prefix; 196 let suffix = archOptions[archName].suffix; 197 assertEq(true, encoding.length > 0, `bad instruction encoding: ${encoding}`); 198 assertEq(true, prefix.length > 0, `bad prefix: ${prefix}`); 199 assertEq(true, suffix.length > 0, `bad suffix: ${suffix}`); 200 201 // Get the expected output string, or skip the test if no expected output 202 // has been provided. Note, because of the assertion near the top of this 203 // file, this will currently only allow arm(32) tests to be skipped. 204 let expected = ""; 205 if (expectedAllTargets.hasOwnProperty(archName)) { 206 expected = expectedAllTargets[archName]; 207 } else { 208 // Paranoia. Don't want to silently skip tests due to logic bugs above. 209 assertEq(archName, "arm"); 210 if (options.log) { 211 print("---- !! no expected output for target, skipping !!"); 212 } 213 return; 214 } 215 216 // Finalise the expected-result string, and stash the original for 217 // debug-printing. 218 expectedInitial = expected; 219 if (!options.no_prefix) { 220 expected = prefix + '\n' + expected; 221 } 222 if (!options.no_suffix) { 223 expected = expected + '\n' + suffix; 224 } 225 expected = fixlines(expected); 226 227 // Compile the test case and collect disassembly output. 228 let ins = wasmEvalText(module_text, {}, options.features); 229 if (options.instanceBox) 230 options.instanceBox.value = ins; 231 let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true}); 232 let output_simple = stripencoding(output, encoding); 233 234 // Check for success, print diagnostics 235 let output_matches_expected = output_simple.match(new RegExp(expected)) != null; 236 if (!output_matches_expected) { 237 print("---- adhoc-tier1-test.js: TEST FAILED ----"); 238 } 239 if (options.log && output_matches_expected) { 240 print("---- adhoc-tier1-test.js: TEST PASSED ----"); 241 } 242 if (options.log || !output_matches_expected) { 243 print("---- module text"); 244 print(module_text); 245 print("---- actual"); 246 print(output); 247 print("---- expected (initial)"); 248 print(expectedInitial); 249 print("---- expected (as used)"); 250 print(expected); 251 print("----"); 252 } 253 254 // Finally, the whole point of this: 255 assertEq(output_matches_expected, true); 256 }