tor-browser

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

wasm.js (19894B)


      1 if (!wasmIsSupported())
      2    quit();
      3 
      4 load(libdir + "asserts.js");
      5 
      6 function canRunHugeMemoryTests() {
      7    // We're aiming for 64-bit desktop builds with no interesting analysis
      8    // running that might inflate memory consumption unreasonably.  It's OK if
      9    // they're debug builds, though.
     10    //
     11    // The build configuration object may be extended at any time with new
     12    // properties, so neither an allowlist of properties that can be true or a
     13    // blocklist of properties that can't be true is great.  But the latter is
     14    // probably better.
     15    let blocked = ['rooting-analysis','simulator',
     16                   'android','wasi','asan','tsan','ubsan','valgrind'];
     17    for ( let b of blocked ) {
     18        if (getBuildConfiguration(b)) {
     19            print("Failing canRunHugeMemoryTests() because '" + b + "' is true");
     20            return false;
     21        }
     22    }
     23    if (getBuildConfiguration("pointer-byte-size") != 8) {
     24        print("Failing canRunHugeMemoryTests() because the build is not 64-bit");
     25        return false;
     26    }
     27    return true;
     28 }
     29 
     30 // On 64-bit systems with explicit bounds checking, ion and baseline can handle
     31 // 65536 pages.
     32 
     33 var PageSizeInBytes = 65536;
     34 var MaxBytesIn32BitMemory = 0;
     35 if (largeArrayBufferSupported()) {
     36    MaxBytesIn32BitMemory = 65536*PageSizeInBytes;
     37 } else {
     38    // This is an overestimate twice: first, the max byte value is divisible by
     39    // the page size; second, it must be a valid bounds checking immediate.  But
     40    // INT32_MAX is fine for testing.
     41    MaxBytesIn32BitMemory = 0x7FFF_FFFF;
     42 }
     43 var MaxPagesIn32BitMemory = Math.floor(MaxBytesIn32BitMemory / PageSizeInBytes);
     44 
     45 function wasmEvalBinary(binary, imports, compileOptions) {
     46    let valid = WebAssembly.validate(binary, compileOptions);
     47 
     48    let m;
     49    try {
     50        m = new WebAssembly.Module(binary, compileOptions);
     51        assertEq(valid, true, "failed WebAssembly.validate but still compiled successfully");
     52    } catch(e) {
     53        if (!e.toString().match(/out of memory/)) {
     54            assertEq(valid, false, `passed WebAssembly.validate but failed to compile: ${e}`);
     55        }
     56        throw e;
     57    }
     58 
     59    return new WebAssembly.Instance(m, imports);
     60 }
     61 
     62 function wasmEvalText(str, imports, compileOptions) {
     63    return wasmEvalBinary(wasmTextToBinary(str), imports, compileOptions);
     64 }
     65 
     66 function wasmValidateBinary(binary) {
     67    let valid = WebAssembly.validate(binary);
     68    if (!valid) {
     69        new WebAssembly.Module(binary);
     70        throw new Error("module failed WebAssembly.validate but compiled successfully");
     71    }
     72    assertEq(valid, true, "wasm module was invalid");
     73 }
     74 
     75 function wasmFailValidateBinary(binary, pattern) {
     76    assertEq(WebAssembly.validate(binary), false, "module passed WebAssembly.validate when it should not have");
     77    assertErrorMessage(() => new WebAssembly.Module(binary), WebAssembly.CompileError, pattern, "module failed WebAssembly.validate but did not fail to compile in the expected way");
     78 }
     79 
     80 function wasmValidateText(str) {
     81    return wasmValidateBinary(wasmTextToBinary(str));
     82 }
     83 
     84 function wasmFailValidateText(str, pattern) {
     85    return wasmFailValidateBinary(wasmTextToBinary(str), pattern);
     86 }
     87 
     88 // Expected compilation failure can happen in a couple of ways:
     89 //
     90 // - The compiler can be available but not capable of recognizing some opcodes:
     91 //   Compilation will start, but will fail with a CompileError.  This is what
     92 //   happens without --wasm-gc if opcodes enabled by --wasm-gc are used.
     93 //
     94 // - The compiler can be unavailable: Compilation will not start at all but will
     95 //   throw an Error.  This is what happens with "--wasm-gc --wasm-compiler=X" if
     96 //   X does not support the features enabled by --wasm-gc.
     97 
     98 function wasmCompilationShouldFail(bin, compile_error_regex) {
     99    try {
    100        new WebAssembly.Module(bin);
    101    } catch (e) {
    102        if (e instanceof WebAssembly.CompileError) {
    103            assertEq(compile_error_regex.test(e), true);
    104        } else if (e instanceof Error) {
    105            assertEq(/can't use wasm debug\/gc without baseline/.test(e), true);
    106        } else {
    107            throw new Error("Unexpected exception value:\n" + e);
    108        }
    109    }
    110 }
    111 
    112 function mismatchError(actual, expect) {
    113    var str = `(type mismatch: expression has type ${actual} but expected ${expect})|` +
    114              `(type mismatch: expected ${expect}, found ${actual}\)`;
    115    return RegExp(str);
    116 }
    117 
    118 const emptyStackError = /(from empty stack)|(nothing on stack)/;
    119 const unusedValuesError = /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/;
    120 
    121 function jsify(wasmVal) {
    122    if (wasmVal === 'nan')
    123        return NaN;
    124    if (wasmVal === 'inf')
    125        return Infinity;
    126    if (wasmVal === '-inf')
    127        return Infinity;
    128    if (wasmVal === '-0')
    129        return -0;
    130    return wasmVal;
    131 }
    132 
    133 function _augmentSrc(src, assertions) {
    134    let i = 0;
    135    let newSrc = src.substr(0, src.lastIndexOf(')'));
    136    for (let { func, args, expected, type } of assertions) {
    137        newSrc += `
    138        (func (export "assert_${i++}") (result i32)
    139         ${ args ? args.join('\n') : '' }
    140         call ${func}`;
    141 
    142        if (typeof expected !== 'undefined') {
    143            switch (type) {
    144                case 'f32':
    145                    newSrc += `
    146         i32.reinterpret_f32
    147         ${(function () {
    148             if (expected == 'nan:arithmetic') {
    149               expected = '0x7FC00000';
    150               return '(i32.const 0x7FC00000) i32.and';
    151             }
    152             return '';
    153         })()}
    154         i32.const ${expected}
    155         i32.eq`;
    156                    break;
    157                case 'f64':
    158                    newSrc += `
    159         i64.reinterpret_f64
    160         ${(function () {
    161             if (expected == 'nan:arithmetic') {
    162               expected = '0x7FF8000000000000';
    163               return '(i64.const 0x7FF8000000000000) i64.and';
    164             }
    165             return '';
    166         })()}
    167         i64.const ${expected}
    168         i64.eq`;
    169                    break;
    170                case 'i32':
    171                    newSrc += `
    172         i32.const ${expected}
    173         i32.eq`;
    174                    break;
    175                case 'i64':
    176                    newSrc += `
    177         i64.const ${expected}
    178         i64.eq`;
    179                    break;
    180                case 'v128':
    181                    newSrc += `
    182         v128.const ${expected}
    183         i8x16.eq
    184         i8x16.all_true`;
    185                    break;
    186                default:
    187                    throw new Error("unexpected usage of wasmAssert");
    188            }
    189        } else {
    190            // Always true when there's no expected return value.
    191            newSrc += "\ni32.const 1";
    192        }
    193 
    194        newSrc += ')\n';
    195    }
    196    newSrc += ')';
    197    return newSrc;
    198 }
    199 
    200 function wasmAssert(src, assertions, maybeImports = {}, exportBox = null) {
    201    let { exports } = wasmEvalText(_augmentSrc(src, assertions), maybeImports);
    202    if (exportBox !== null)
    203        exportBox.exports = exports;
    204    for (let i = 0; i < assertions.length; i++) {
    205        let { func, expected, params } = assertions[i];
    206        let paramText = params ? params.join(', ') : '';
    207        assertEq(exports[`assert_${i}`](), 1,
    208                 `Unexpected value when running ${func}(${paramText}), expecting ${expected}.`);
    209    }
    210 }
    211 
    212 // Fully test a module:
    213 // - ensure it validates.
    214 // - ensure it compiles and produces the expected result.
    215 // - ensure textToBinary(binaryToText(binary)) = binary
    216 // Preconditions:
    217 // - the binary module must export a function called "run".
    218 function wasmFullPass(text, expected, maybeImports, ...args) {
    219    let binary = wasmTextToBinary(text);
    220    assertEq(WebAssembly.validate(binary), true, "Must validate.");
    221 
    222    let module = new WebAssembly.Module(binary);
    223    let instance = new WebAssembly.Instance(module, maybeImports);
    224    assertEq(typeof instance.exports.run, 'function', "A 'run' function must be exported.");
    225    assertEq(instance.exports.run(...args), expected, "Initial module must return the expected result.");
    226 }
    227 
    228 // Ditto, but expects a function named '$run' instead of exported with this name.
    229 function wasmFullPassI64(text, expected, maybeImports, ...args) {
    230    let binary = wasmTextToBinary(text);
    231    assertEq(WebAssembly.validate(binary), true, "Must validate.");
    232 
    233    let augmentedSrc = _augmentSrc(text, [ { type: 'i64', func: '$run', args, expected } ]);
    234    let augmentedBinary = wasmTextToBinary(augmentedSrc);
    235 
    236    let module = new WebAssembly.Module(augmentedBinary);
    237    let instance = new WebAssembly.Instance(module, maybeImports);
    238    assertEq(instance.exports.assert_0(), 1);
    239 }
    240 
    241 function wasmRunWithDebugger(wast, lib, init, done) {
    242    let g = newGlobal({newCompartment: true});
    243    let dbg = new Debugger(g);
    244 
    245    g.eval(`
    246 var wasm = wasmTextToBinary(\`${wast}\`);
    247 var lib = ${lib || 'undefined'};
    248 var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`);
    249 
    250    var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
    251 
    252    init({dbg, wasmScript, g,});
    253    let result = undefined, error = undefined;
    254    try {
    255        result = g.eval("m.exports.test()");
    256    } catch (ex) {
    257        error = ex;
    258    }
    259    done({dbg, result, error, wasmScript, g,});
    260 }
    261 
    262 const WasmHelpers = {};
    263 
    264 (function() {
    265    let enabled = false;
    266    try {
    267        enableSingleStepProfiling();
    268        disableSingleStepProfiling();
    269        enabled = true;
    270    } catch (e) {}
    271    WasmHelpers.isSingleStepProfilingEnabled = enabled;
    272 })();
    273 
    274 // The cache of matched and unmatched strings seriously speeds up matching on
    275 // the emulators and makes tests time out less often.
    276 
    277 var matched = {};
    278 var unmatched = {};
    279 
    280 WasmHelpers._normalizeStack = (stack, preciseStacks) => {
    281    var wasmFrameTypes = [
    282        {re:/^jit call to int64(?: or v128)? wasm function$/,             sub:"i64>"},
    283        {re:/^out-of-line coercion for jit entry arguments \(in wasm\)$/, sub:"ool>"},
    284        {re:/^wasm-function\[(\d+)\] \(.*\)$/,                            sub:"$1"},
    285        {re:/^(\w+) \(.*?> WebAssembly\.Module:\d+\)$/,                   sub:"$1"},
    286        {re:/^(fast|slow) exit trampoline (?:to native )?\(in wasm\)$/,   sub:"<"},
    287        {re:/^call to(?: asm.js)? native (.*?)(?: builtin)? \(in wasm\)$/, sub:"$1"},
    288        {re:/^call to native (.*)$/,                                      sub:"#$1"},
    289        {re:/^tier-up request \(in wasm\)$/,                              sub: ""},
    290        {re:/ \(in wasm\)$/,                                              sub:""},
    291    ];
    292 
    293    let entryRegexps;
    294    if (preciseStacks) {
    295        entryRegexps = [
    296            {re:/^slow entry trampoline \(in wasm\)$/,                    sub:"!>"},
    297            {re:/^fast entry trampoline \(in wasm\)$/,                    sub:">"},
    298        ];
    299    } else {
    300        entryRegexps = [
    301            {re:/^(fast|slow) entry trampoline \(in wasm\)$/,             sub:">"}
    302        ];
    303    }
    304    wasmFrameTypes = entryRegexps.concat(wasmFrameTypes);
    305 
    306    var framesIn = stack.split(',');
    307    var framesOut = [];
    308  outer:
    309    for (let frame of framesIn) {
    310        if (unmatched[frame])
    311            continue;
    312        let probe = matched[frame];
    313        if (probe !== undefined) {
    314            framesOut.push(probe);
    315            continue;
    316        }
    317        for (let {re, sub} of wasmFrameTypes) {
    318            if (re.test(frame)) {
    319                let repr = frame.replace(re, sub);
    320                framesOut.push(repr);
    321                matched[frame] = repr;
    322                continue outer;
    323            }
    324        }
    325        unmatched[frame] = true;
    326    }
    327 
    328    return framesOut.join(',');
    329 };
    330 
    331 WasmHelpers._removeAdjacentDuplicates = array => {
    332    if (array.length < 2)
    333        return;
    334    let i = 0;
    335    for (let j = 1; j < array.length; j++) {
    336        if (array[i] !== array[j])
    337            array[++i] = array[j];
    338    }
    339    array.length = i + 1;
    340 }
    341 
    342 WasmHelpers.normalizeStacks = (stacks, preciseStacks = false) => {
    343    let observed = [];
    344    for (let i = 0; i < stacks.length; i++)
    345        observed[i] = WasmHelpers._normalizeStack(stacks[i], preciseStacks);
    346    WasmHelpers._removeAdjacentDuplicates(observed);
    347    return observed;
    348 };
    349 
    350 WasmHelpers._compareStacks = (got, expect) => {
    351    if (got.length != expect.length) {
    352        return false;
    353    }
    354    for (let i = 0; i < got.length; i++) {
    355        if (got[i] !== expect[i])
    356            return false;
    357    }
    358    return true;
    359 }
    360 
    361 WasmHelpers.assertEqImpreciseStacks = (got, expect) => {
    362    let observed = WasmHelpers.normalizeStacks(got, /* precise */ false);
    363    let same = WasmHelpers._compareStacks(observed, expect);
    364    if (!same) {
    365        if (observed.length != expect.length) {
    366            print(`Got:\n${observed.toSource()}\nExpect:\n${expect.toSource()}`);
    367            assertEq(observed.length, expect.length);
    368        }
    369        for (let i = 0; i < observed.length; i++) {
    370            if (observed[i] !== expect[i]) {
    371                print(`On stack ${i}, Got:\n${observed[i]}\nExpect:\n${expect[i]}`);
    372                assertEq(observed[i], expect[i]);
    373            }
    374        }
    375    }
    376 }
    377 
    378 WasmHelpers.extractStackFrameFunction = (frameString) => {
    379    var [_, name, filename, line, column] = frameString.match(/^(.*)@(.*):(.*):(.*)$/);
    380    if (name)
    381        return name;
    382    if (/wasm-function/.test(line))
    383        return line;
    384    return "";
    385 };
    386 
    387 WasmHelpers.assertStackTrace = (exception, expected) => {
    388    let callsites = exception.stack.trim().split('\n').map(WasmHelpers.extractStackFrameFunction);
    389    assertEq(callsites.length, expected.length);
    390    for (let i = 0; i < callsites.length; i++) {
    391        assertEq(callsites[i], expected[i]);
    392    }
    393 };
    394 
    395 WasmHelpers.nextLineNumber = (n=1) => {
    396    return +(new Error().stack).split('\n')[1].split(':')[1] + n;
    397 }
    398 
    399 WasmHelpers.startProfiling = () => {
    400    if (!WasmHelpers.isSingleStepProfilingEnabled)
    401        return;
    402    enableSingleStepProfiling();
    403 }
    404 
    405 WasmHelpers.endProfiling = () => {
    406    if (!WasmHelpers.isSingleStepProfilingEnabled)
    407        return;
    408    return disableSingleStepProfiling();
    409 }
    410 
    411 WasmHelpers.assertEqPreciseStacks = (observed, expectedStacks) => {
    412    if (!WasmHelpers.isSingleStepProfilingEnabled)
    413        return null;
    414 
    415    observed = WasmHelpers.normalizeStacks(observed, /* precise */ true);
    416 
    417    for (let i = 0; i < expectedStacks.length; i++) {
    418        if (WasmHelpers._compareStacks(observed, expectedStacks[i]))
    419            return i;
    420    }
    421 
    422    throw new Error(`no plausible stacks found, observed: ${observed.join('/')}
    423 Expected one of:
    424 ${expectedStacks.map(stacks => stacks.join("/")).join('\n')}`);
    425 }
    426 
    427 function fuzzingSafe() {
    428    return typeof getErrorNotes == 'undefined';
    429 }
    430 
    431 // Common instantiations of wasm values for dynamic type check testing
    432 
    433 // Valid values for funcref
    434 let WasmFuncrefValues = [
    435    wasmEvalText(`(module (func (export "")))`).exports[''],
    436 ];
    437 
    438 // Valid values for structref/arrayref
    439 let WasmStructrefValues = [];
    440 let WasmArrayrefValues = [];
    441 let { newStruct, newArray } = wasmEvalText(`
    442    (module
    443    (type $s (sub (struct)))
    444    (type $a (sub (array i32)))
    445    (func (export "newStruct") (result anyref)
    446        struct.new $s)
    447    (func (export "newArray") (result anyref)
    448        i32.const 0
    449        i32.const 0
    450        array.new $a)
    451    )`).exports;
    452 WasmStructrefValues.push(newStruct());
    453 WasmArrayrefValues.push(newArray());
    454 
    455 let WasmGcObjectValues = WasmStructrefValues.concat(WasmArrayrefValues);
    456 
    457 // Valid values for eqref
    458 let WasmEqrefValues = [...WasmStructrefValues, ...WasmArrayrefValues];
    459 
    460 // Valid values for i31ref
    461 let MinI31refValue = -1 * Math.pow(2, 30);
    462 let MaxI31refValue = Math.pow(2, 30) - 1;
    463 let WasmI31refValues = [
    464  // first four 31-bit signed numbers
    465  MinI31refValue,
    466  MinI31refValue + 1,
    467  MinI31refValue + 2,
    468  MinI31refValue + 3,
    469  // five numbers around zero
    470  -2,
    471  -1,
    472  0,
    473  1,
    474  2,
    475  // last four 31-bit signed numbers
    476  MaxI31refValue - 3,
    477  MaxI31refValue - 2,
    478  MaxI31refValue - 1,
    479  MaxI31refValue,
    480 ];
    481 
    482 // Valid and invalid values for anyref
    483 let WasmAnyrefValues = [...WasmEqrefValues, ...WasmI31refValues];
    484 let WasmNonAnyrefValues = [
    485    undefined,
    486    true,
    487    false,
    488    {x:1337},
    489    ["abracadabra"],
    490    13.37,
    491    0x7fffffff + 0.1,
    492    -0x7fffffff - 0.1,
    493    0x80000000 + 0.1,
    494    -0x80000000 - 0.1,
    495    0xffffffff + 0.1,
    496    -0xffffffff - 0.1,
    497    Number.EPSILON,
    498    Number.MAX_SAFE_INTEGER,
    499    Number.MIN_SAFE_INTEGER,
    500    Number.MIN_VALUE,
    501    Number.MAX_VALUE,
    502    Number.NaN,
    503    "hi",
    504    37n,
    505    new Number(42),
    506    new Boolean(true),
    507    Symbol("status"),
    508    () => 1337,
    509    ...WasmFuncrefValues,
    510 ];
    511 
    512 // Valid externref values
    513 let WasmNonNullExternrefValues = [
    514    ...WasmNonAnyrefValues,
    515    ...WasmAnyrefValues
    516 ];
    517 let WasmExternrefValues = [null, ...WasmNonNullExternrefValues];
    518 
    519 // Max number of memories in a single wasm module.
    520 let MaxMemories = 100
    521 
    522 // Constants related to memory sizes.
    523 const MaxMemory64PagesValidation = BigInt(Math.pow(2, 37) - 1); // from spec
    524 const MaxTable64ElemsValidation = 0xFFFF_FFFF_FFFF_FFFFn; // from spec
    525 const MaxTableElemsRuntime = 10000000; // from WasmConstants.h
    526 const MaxUint32 = 0xFFFF_FFFF;
    527 
    528 // Common array utilities
    529 
    530 // iota(n,k) creates an Array of length n with values k..k+n-1
    531 function iota(len, k=0) {
    532    let xs = [];
    533    for ( let i=0 ; i < len ; i++ )
    534        xs.push(i+k);
    535    return xs;
    536 }
    537 
    538 // cross(A) where A is an array of length n creates an Array length n*n of
    539 // two-element Arrays representing all pairs of elements of A.
    540 function cross(xs) {
    541    let results = [];
    542    for ( let x of xs )
    543        for ( let y of xs )
    544            results.push([x,y]);
    545    return results;
    546 }
    547 
    548 // Remove all values equal to v from an array xs, comparing equal for NaN.
    549 function remove(v, xs) {
    550    let result = [];
    551    for ( let w of xs ) {
    552        if (v === w || isNaN(v) && isNaN(w))
    553            continue;
    554        result.push(w);
    555    }
    556    return result;
    557 }
    558 
    559 // permute(A) where A is an Array returns an Array of Arrays, each inner Array a
    560 // distinct permutation of the elements of A.  A is assumed not to have any
    561 // elements that are pairwise equal in the sense of remove().
    562 function permute(xs) {
    563    if (xs.length == 1)
    564        return [xs];
    565    let results = [];
    566    for (let v of xs)
    567        for (let tail of permute(remove(v, xs)))
    568            results.push([v, ...tail]);
    569    return results;
    570 }
    571 
    572 // interleave([a,b,c,...],[0,1,2,...]) => [a,0,b,1,c,2,...]
    573 function interleave(xs, ys) {
    574    assertEq(xs.length, ys.length);
    575    let res = [];
    576    for ( let i=0 ; i < xs.length; i++ ) {
    577        res.push(xs[i]);
    578        res.push(ys[i]);
    579    }
    580    return res;
    581 }
    582 
    583 // assertSame([a,...],[b,...]) asserts that the two arrays have the same length
    584 // and that they element-wise assertEq IGNORING Number/BigInt differences.  This
    585 // predicate is in this file because it is wasm-specific.
    586 function assertSame(got, expected) {
    587    assertEq(got.length, expected.length);
    588    for ( let i=0; i < got.length; i++ ) {
    589        let g = got[i];
    590        let e = expected[i];
    591        if (typeof g != typeof e) {
    592            if (typeof g == "bigint")
    593                e = BigInt(e);
    594            else if (typeof e == "bigint")
    595                g = BigInt(g);
    596        }
    597        assertEq(g, e);
    598    }
    599 }
    600 
    601 // assertEqResults([a,...],[b,...]) asserts that the two results from a wasm
    602 // call are the same. This will compare deeply inside the result array, and
    603 // relax a mismatch around single element arrays.
    604 //
    605 // This predicate is in this file because it is wasm-specific.
    606 function assertEqResults(got, expected) {
    607    if (!Array.isArray(got)) {
    608        got = [got];
    609    }
    610    if (!Array.isArray(expected)) {
    611        expected = [expected];
    612    }
    613    assertSame(got, expected);
    614 }
    615 
    616 // TailCallIterations is selected to be large enough to trigger
    617 // "too much recursion", but not to be slow.
    618 var TailCallIterations = getBuildConfiguration("simulator") ? 1000 : 100000;
    619 // TailCallBallast is selected to spill registers as parameters.
    620 var TailCallBallast = 30;