tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }