tor-browser

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

ion-analysis.js (34312B)


      1 // |jit-test| skip-if: !wasmSimdEnabled() || wasmCompileMode() != "ion" || !this.wasmSimdAnalysis
      2 
      3 // White-box tests for SIMD optimizations.  These are sensitive to internal
      4 // details of the front-end and lowering logic, which is partly platform-dependent.
      5 //
      6 // In DEBUG builds, the testing function wasmSimdAnalysis() returns a string
      7 // describing the last decision made by the SIMD lowering code: to perform an
      8 // optimized lowering or the default byte shuffle+blend for i8x16.shuffle; to
      9 // shift by a constant or a variable for the various shifts; and so on.
     10 //
     11 // We test that the expected transformation applies, and that the machine code
     12 // generates the expected result.
     13 
     14 var isArm64 = getBuildConfiguration("arm64");
     15 
     16 // 32-bit permutation that is not a rotation.
     17 let perm32x4_pattern = [4, 5, 6, 7, 12, 13, 14, 15, 8, 9, 10, 11, 0, 1, 2, 3];
     18 
     19 // Operands the same, dword permutation
     20 {
     21    let ins = wasmCompile(`
     22 (module
     23  (memory (export "mem") 1 1)
     24  (func (export "run")
     25    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
     26  (func $f (param v128) (result v128)
     27    (i8x16.shuffle ${perm32x4_pattern.join(' ')} (local.get 0) (local.get 0))))`);
     28 
     29    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
     30 
     31    let mem = new Int8Array(ins.exports.mem.buffer);
     32    set(mem, 16, iota(16));
     33    ins.exports.run();
     34    assertSame(get(mem, 0, 16), perm32x4_pattern);
     35 }
     36 
     37 // Right operand ignored, dword permutation
     38 {
     39    let ins = wasmCompile(`
     40 (module
     41  (memory (export "mem") 1 1)
     42  (func (export "run")
     43    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
     44  (func $f (param v128) (param v128) (result v128)
     45    (i8x16.shuffle ${perm32x4_pattern.join(' ')} (local.get 0) (local.get 1))))`);
     46 
     47    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
     48 
     49    let mem = new Int8Array(ins.exports.mem.buffer);
     50    set(mem, 16, iota(16));
     51    set(mem, 32, iota(16).map(x => x+16));
     52    ins.exports.run();
     53    assertSame(get(mem, 0, 16), perm32x4_pattern);
     54 }
     55 
     56 // Left operand ignored, dword permutation
     57 {
     58    let ins = wasmCompile(`
     59 (module
     60  (memory (export "mem") 1 1)
     61  (func (export "run")
     62    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
     63  (func $f (param v128) (param v128) (result v128)
     64    (i8x16.shuffle ${perm32x4_pattern.map(x => x+16).join(' ')} (local.get 0) (local.get 1))))`);
     65 
     66    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
     67 
     68    let mem = new Int8Array(ins.exports.mem.buffer);
     69    set(mem, 16, iota(16).map(x => x+16));
     70    set(mem, 32, iota(16));
     71    ins.exports.run();
     72    assertSame(get(mem, 0, 16), perm32x4_pattern);
     73 }
     74 
     75 // Operands the same, word permutation on both sides of the qword divide, with a qword swap
     76 {
     77    let perm16x8_pattern = [12, 13, 14, 15, 10, 11, 8, 9,
     78                             6,  7,  4,  5,  2,  3, 0, 1];
     79    let ins = wasmCompile(`
     80 (module
     81  (memory (export "mem") 1 1)
     82  (func (export "run")
     83    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
     84  (func $f (param v128) (result v128)
     85    (i8x16.shuffle ${perm16x8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
     86 
     87    assertEq(wasmSimdAnalysis(), "shuffle -> permute 16x8");
     88 
     89    let mem = new Int8Array(ins.exports.mem.buffer);
     90    set(mem, 16, iota(16));
     91    ins.exports.run();
     92    assertSame(get(mem, 0, 16), perm16x8_pattern);
     93 }
     94 
     95 // Operands the same, word permutation on both sides of the qword divide, no qword swap
     96 {
     97    let perm16x8_pattern = [ 6,  7,  4,  5,  2,  3, 0, 1,
     98                            12, 13, 14, 15, 10, 11, 8, 9];
     99    let ins = wasmCompile(`
    100 (module
    101  (memory (export "mem") 1 1)
    102  (func (export "run")
    103    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    104  (func $f (param v128) (result v128)
    105    (i8x16.shuffle ${perm16x8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    106 
    107    assertEq(wasmSimdAnalysis(), "shuffle -> permute 16x8");
    108 
    109    let mem = new Int8Array(ins.exports.mem.buffer);
    110    set(mem, 16, iota(16));
    111    ins.exports.run();
    112    assertSame(get(mem, 0, 16), perm16x8_pattern);
    113 }
    114 
    115 // Operands the same, word permutation on low side of the qword divide, no qword swap
    116 {
    117    let perm16x8_pattern = [ 6, 7,  4,  5,  2,  3,  0,  1,
    118                             8, 9, 10, 11, 12, 13, 14, 15];
    119    let ins = wasmCompile(`
    120 (module
    121  (memory (export "mem") 1 1)
    122  (func (export "run")
    123    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    124  (func $f (param v128) (result v128)
    125    (i8x16.shuffle ${perm16x8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    126 
    127    assertEq(wasmSimdAnalysis(), "shuffle -> permute 16x8");
    128 
    129    let mem = new Int8Array(ins.exports.mem.buffer);
    130    set(mem, 16, iota(16));
    131    ins.exports.run();
    132    assertSame(get(mem, 0, 16), perm16x8_pattern);
    133 }
    134 
    135 // Operands the same, word permutation on high side of the qword divide, no qword swap
    136 {
    137    let perm16x8_pattern = [ 0,  1,  2,  3,  4,  5, 6, 7,
    138                            12, 13, 14, 15, 10, 11, 8, 9];
    139    let ins = wasmCompile(`
    140 (module
    141  (memory (export "mem") 1 1)
    142  (func (export "run")
    143    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    144  (func $f (param v128) (result v128)
    145    (i8x16.shuffle ${perm16x8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    146 
    147    assertEq(wasmSimdAnalysis(), "shuffle -> permute 16x8");
    148 
    149    let mem = new Int8Array(ins.exports.mem.buffer);
    150    set(mem, 16, iota(16));
    151    ins.exports.run();
    152    assertSame(get(mem, 0, 16), perm16x8_pattern);
    153 }
    154 
    155 // Same operands, byte rotate
    156 {
    157    // 8-bit permutation that is a rotation
    158    let rot8x16_pattern = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4];
    159    let ins = wasmCompile(`
    160 (module
    161  (memory (export "mem") 1 1)
    162  (func (export "run")
    163    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    164  (func $f (param v128) (result v128)
    165    (i8x16.shuffle ${rot8x16_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    166 
    167    assertEq(wasmSimdAnalysis(), "shuffle -> rotate-right 8x16");
    168 
    169    let mem = new Int8Array(ins.exports.mem.buffer);
    170    set(mem, 16, iota(16));
    171    ins.exports.run();
    172    assertSame(get(mem, 0, 16), rot8x16_pattern);
    173 }
    174 
    175 // Operands the same, random jumble => byte permutation
    176 {
    177    // 8-bit permutation that is not a rotation
    178    let perm8x16_pattern = [5, 7, 6, 8, 9, 10, 11, 4, 13, 14, 15, 0, 1, 2, 3, 12];
    179    let ins = wasmCompile(`
    180 (module
    181  (memory (export "mem") 1 1)
    182  (func (export "run")
    183    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    184  (func $f (param v128) (result v128)
    185    (i8x16.shuffle ${perm8x16_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    186 
    187    assertEq(wasmSimdAnalysis(), "shuffle -> permute 8x16");
    188 
    189    let mem = new Int8Array(ins.exports.mem.buffer);
    190    set(mem, 16, iota(16));
    191    ins.exports.run();
    192    assertSame(get(mem, 0, 16), perm8x16_pattern);
    193 }
    194 
    195 // Operands differ, both accessed, rhs is constant zero, left-shift pattern
    196 {
    197    // 8-bit shift with zeroes shifted in at the right end
    198    let shift8x16_pattern = [16, 16, 16, 16, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    199    let ins = wasmCompile(`
    200 (module
    201  (memory (export "mem") 1 1)
    202  (func (export "run")
    203    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    204  (func $f (param v128) (result v128)
    205    (i8x16.shuffle ${shift8x16_pattern.join(' ')} (local.get 0) (v128.const i32x4 0 0 0 0))))`);
    206 
    207    assertEq(wasmSimdAnalysis(), "shuffle -> shift-left 8x16");
    208 
    209    let mem = new Int8Array(ins.exports.mem.buffer);
    210    set(mem, 16, iota(16));
    211    ins.exports.run();
    212    assertSame(get(mem, 0, 16), shift8x16_pattern.map(x => x >= 16 ? 0 : x));
    213 }
    214 
    215 // The same as above but the constant is lhs.
    216 {
    217    // 8-bit shift with zeroes shifted in at the right end
    218    let shift8x16_pattern = [16, 16, 16, 16, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => x ^ 16);
    219    let ins = wasmCompile(`
    220 (module
    221  (memory (export "mem") 1 1)
    222  (func (export "run")
    223    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    224  (func $f (param v128) (result v128)
    225    (i8x16.shuffle ${shift8x16_pattern.join(' ')} (v128.const i32x4 0 0 0 0) (local.get 0))))`);
    226 
    227    assertEq(wasmSimdAnalysis(), "shuffle -> shift-left 8x16");
    228 
    229    let mem = new Int8Array(ins.exports.mem.buffer);
    230    set(mem, 16, iota(16));
    231    ins.exports.run();
    232    assertSame(get(mem, 0, 16), shift8x16_pattern.map(x => x < 16 ? 0 : x - 16));
    233 }
    234 
    235 // Operands differ, both accessed, rhs is constant zero, left-shift pattern that
    236 // does not start properly.
    237 {
    238    // 8-bit shift with zeroes shifted in at the right end
    239    let shift8x16_pattern = [16, 16, 16, 16, 16, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    240    let ins = wasmCompile(`
    241 (module
    242  (memory (export "mem") 1 1)
    243  (func (export "run")
    244    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    245  (func $f (param v128) (result v128)
    246    (i8x16.shuffle ${shift8x16_pattern.join(' ')} (local.get 0) (v128.const i32x4 0 0 0 0))))`);
    247 
    248    assertEq(wasmSimdAnalysis(), "shuffle -> shuffle+blend 8x16");
    249 
    250    let mem = new Int8Array(ins.exports.mem.buffer);
    251    set(mem, 16, iota(16));
    252    ins.exports.run();
    253    assertSame(get(mem, 0, 16), shift8x16_pattern.map(x => x >= 16 ? 0 : x));
    254 }
    255 
    256 // Operands differ, both accessed, rhs is constant zero, right-shift pattern
    257 {
    258    // 8-bit shift with zeroes shifted in at the right end
    259    let shift8x16_pattern = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 20, 20, 20, 20, 20];
    260    let ins = wasmCompile(`
    261 (module
    262  (memory (export "mem") 1 1)
    263  (func (export "run")
    264    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    265  (func $f (param v128) (result v128)
    266    (i8x16.shuffle ${shift8x16_pattern.join(' ')} (local.get 0) (v128.const i32x4 0 0 0 0))))`);
    267 
    268    assertEq(wasmSimdAnalysis(), "shuffle -> shift-right 8x16");
    269 
    270    let mem = new Int8Array(ins.exports.mem.buffer);
    271    set(mem, 16, iota(16));
    272    ins.exports.run();
    273    assertSame(get(mem, 0, 16), shift8x16_pattern.map(x => x >= 16 ? 0 : x));
    274 }
    275 
    276 // Operands differ, both accessed, rhs is constant zero, right-shift pattern
    277 // that does not end properly.
    278 {
    279    // 8-bit shift with zeroes shifted in at the right end
    280    let shift8x16_pattern = [6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 20, 20, 20, 20, 20, 20];
    281    let ins = wasmCompile(`
    282 (module
    283  (memory (export "mem") 1 1)
    284  (func (export "run")
    285    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    286  (func $f (param v128) (result v128)
    287    (i8x16.shuffle ${shift8x16_pattern.join(' ')} (local.get 0) (v128.const i32x4 0 0 0 0))))`);
    288 
    289    assertEq(wasmSimdAnalysis(), "shuffle -> shuffle+blend 8x16");
    290 
    291    let mem = new Int8Array(ins.exports.mem.buffer);
    292    set(mem, 16, iota(16));
    293    ins.exports.run();
    294    assertSame(get(mem, 0, 16), shift8x16_pattern.map(x => x >= 16 ? 0 : x));
    295 }
    296 
    297 // Operands differ and are variable, both accessed, (lhs ++ rhs) >> k
    298 {
    299    let concat8x16_pattern = [27, 28, 29, 30, 31, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    300    let ins = wasmCompile(`
    301 (module
    302  (memory (export "mem") 1 1)
    303  (func (export "run")
    304    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    305  (func $f (param v128) (param v128) (result v128)
    306    (i8x16.shuffle ${concat8x16_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    307 
    308    assertEq(wasmSimdAnalysis(), "shuffle -> concat+shift-right 8x16");
    309 
    310    let mem = new Int8Array(ins.exports.mem.buffer);
    311    set(mem, 16, iota(16));
    312    set(mem, 32, iota(16).map(k => k+16));
    313    ins.exports.run();
    314    assertSame(get(mem, 0, 16), concat8x16_pattern);
    315 }
    316 
    317 // Operands differ and are variable, both accessed, (rhs ++ lhs) >> k
    318 {
    319    let concat8x16_pattern = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26];
    320    let ins = wasmCompile(`
    321 (module
    322  (memory (export "mem") 1 1)
    323  (func (export "run")
    324    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    325  (func $f (param v128) (param v128) (result v128)
    326    (i8x16.shuffle ${concat8x16_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    327 
    328    assertEq(wasmSimdAnalysis(), "shuffle -> concat+shift-right 8x16");
    329 
    330    let mem = new Int8Array(ins.exports.mem.buffer);
    331    set(mem, 16, iota(16));
    332    set(mem, 32, iota(16).map(k => k+16));
    333    ins.exports.run();
    334    assertSame(get(mem, 0, 16), concat8x16_pattern);
    335 }
    336 
    337 // Operands differ, both accessed, but inputs stay in their lanes => byte blend
    338 {
    339    let blend8x16_pattern = iota(16).map(x => (x % 3 == 0) ? x + 16 : x);
    340    let ins = wasmCompile(`
    341 (module
    342  (memory (export "mem") 1 1)
    343  (func (export "run")
    344    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    345  (func $f (param v128) (param v128) (result v128)
    346    (i8x16.shuffle ${blend8x16_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    347 
    348    assertEq(wasmSimdAnalysis(), "shuffle -> blend 8x16");
    349 
    350    let mem = new Int8Array(ins.exports.mem.buffer);
    351    let lhs = iota(16);
    352    let rhs = iota(16).map(x => x+16);
    353    set(mem, 16, lhs);
    354    set(mem, 32, rhs);
    355    ins.exports.run();
    356    assertSame(get(mem, 0, 16), blend8x16_pattern);
    357 }
    358 
    359 // Operands differ, both accessed, but inputs stay in their lanes => word blend
    360 {
    361    let blend16x8_pattern = iota(16).map(x => (x & 2) ? x + 16 : x);
    362    let ins = wasmCompile(`
    363 (module
    364  (memory (export "mem") 1 1)
    365  (func (export "run")
    366    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    367  (func $f (param v128) (param v128) (result v128)
    368    (i8x16.shuffle ${blend16x8_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    369 
    370    assertEq(wasmSimdAnalysis(), "shuffle -> blend 16x8");
    371 
    372    let mem = new Int8Array(ins.exports.mem.buffer);
    373    let lhs = iota(16);
    374    let rhs = iota(16).map(x => x+16);
    375    set(mem, 16, lhs);
    376    set(mem, 32, rhs);
    377    ins.exports.run();
    378    assertSame(get(mem, 0, 16), blend16x8_pattern);
    379 }
    380 
    381 // Interleave i32x4s
    382 for ( let [lhs, rhs, expected] of
    383      [[[0, 1], [4, 5], "shuffle -> interleave-low 32x4"],
    384       [[2, 3], [6, 7], "shuffle -> interleave-high 32x4"]] ) {
    385    for (let swap of [false, true]) {
    386        if (swap)
    387            [lhs, rhs] = [rhs, lhs];
    388        let interleave_pattern = i32ToI8(interleave(lhs, rhs));
    389        let ins = wasmCompile(`
    390 (module
    391  (memory (export "mem") 1 1)
    392  (func (export "run")
    393    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    394  (func $f (param v128) (param v128) (result v128)
    395    (i8x16.shuffle ${interleave_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    396 
    397        assertEq(wasmSimdAnalysis(), expected);
    398 
    399        let mem = new Int8Array(ins.exports.mem.buffer);
    400        let lhsval = iota(16);
    401        let rhsval = iota(16).map(x => x+16);
    402        set(mem, 16, lhsval);
    403        set(mem, 32, rhsval);
    404        ins.exports.run();
    405        assertSame(get(mem, 0, 16), interleave_pattern);
    406    }
    407 }
    408 
    409 // Interleave i64x2s
    410 for ( let [lhs, rhs, expected] of
    411  [[[0], [2], "shuffle -> interleave-low 64x2"],
    412   [[1], [3], "shuffle -> interleave-high 64x2"]] ) {
    413    for (let swap of [false, true]) {
    414        if (swap)
    415            [lhs, rhs] = [rhs, lhs];
    416        let interleave_pattern = i64ToI2(interleave(lhs, rhs));
    417        let ins = wasmCompile(`
    418 (module
    419  (memory (export "mem") 1 1)
    420  (func (export "run")
    421    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    422  (func $f (param v128) (param v128) (result v128)
    423    (i8x16.shuffle ${interleave_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    424 
    425        assertEq(wasmSimdAnalysis(), expected);
    426 
    427        let mem = new Int8Array(ins.exports.mem.buffer);
    428        let lhsval = iota(16);
    429        let rhsval = iota(16).map(x => x+16);
    430        set(mem, 16, lhsval);
    431        set(mem, 32, rhsval);
    432        ins.exports.run();
    433        assertSame(get(mem, 0, 16), interleave_pattern);
    434    }
    435 }
    436 
    437 // Interleave i16x8s
    438 for ( let [lhs, rhs, expected] of
    439      [[[0, 1, 2, 3], [8, 9, 10, 11], "shuffle -> interleave-low 16x8"],
    440       [[4, 5, 6, 7], [12, 13, 14, 15], "shuffle -> interleave-high 16x8"]] ) {
    441    for (let swap of [false, true]) {
    442        if (swap)
    443            [lhs, rhs] = [rhs, lhs];
    444        let interleave_pattern = i16ToI8(interleave(lhs, rhs));
    445        let ins = wasmCompile(`
    446 (module
    447  (memory (export "mem") 1 1)
    448  (func (export "run")
    449    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    450  (func $f (param v128) (param v128) (result v128)
    451    (i8x16.shuffle ${interleave_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    452 
    453        assertEq(wasmSimdAnalysis(), expected);
    454 
    455        let mem = new Int8Array(ins.exports.mem.buffer);
    456        let lhsval = iota(16);
    457        let rhsval = iota(16).map(x => x+16);
    458        set(mem, 16, lhsval);
    459        set(mem, 32, rhsval);
    460        ins.exports.run();
    461        assertSame(get(mem, 0, 16), interleave_pattern);
    462    }
    463 }
    464 
    465 // Interleave i8x16s
    466 for ( let [lhs, rhs, expected] of
    467      [[[0, 1, 2, 3, 4, 5, 6, 7],      [16, 17, 18, 19, 20, 21, 22, 23], "shuffle -> interleave-low 8x16"],
    468       [[8, 9, 10, 11, 12, 13, 14, 15],[24, 25, 26, 27, 28, 29, 30, 31], "shuffle -> interleave-high 8x16"]] ) {
    469    for (let swap of [false, true]) {
    470        if (swap)
    471            [lhs, rhs] = [rhs, lhs];
    472        let interleave_pattern = interleave(lhs, rhs);
    473        let ins = wasmCompile(`
    474 (module
    475  (memory (export "mem") 1 1)
    476  (func (export "run")
    477    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    478  (func $f (param v128) (param v128) (result v128)
    479    (i8x16.shuffle ${interleave_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    480 
    481        assertEq(wasmSimdAnalysis(), expected);
    482 
    483        let mem = new Int8Array(ins.exports.mem.buffer);
    484        let lhsval = iota(16);
    485        let rhsval = iota(16).map(x => x+16);
    486        set(mem, 16, lhsval);
    487        set(mem, 32, rhsval);
    488        ins.exports.run();
    489        assertSame(get(mem, 0, 16), interleave_pattern);
    490    }
    491 }
    492 
    493 // Operands differ, both accessed, random jumble => byte shuffle+blend
    494 {
    495    let blend_perm8x16_pattern = [5, 23, 6, 24, 9, 10, 11, 7, 7, 14, 15, 19, 1, 2, 3, 12];
    496    let ins = wasmCompile(`
    497 (module
    498  (memory (export "mem") 1 1)
    499  (func (export "run")
    500    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    501  (func $f (param v128) (param v128) (result v128)
    502    (i8x16.shuffle ${blend_perm8x16_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    503 
    504    assertEq(wasmSimdAnalysis(), "shuffle -> shuffle+blend 8x16");
    505 
    506    let mem = new Int8Array(ins.exports.mem.buffer);
    507    let lhs = iota(16).map(x => x+16);
    508    let rhs = iota(16);
    509    set(mem, 16, lhs);
    510    set(mem, 32, rhs);
    511    ins.exports.run();
    512    assertSame(get(mem, 0, 16),
    513               blend_perm8x16_pattern.map(x => x < 16 ? lhs[x] : rhs[x-16]));
    514 }
    515 
    516 // No-op, ignoring right operand, should turn into a move.
    517 {
    518    let nop8x16_pattern = iota(16);
    519    let ins = wasmCompile(`
    520 (module
    521  (memory (export "mem") 1 1)
    522  (func (export "run")
    523    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    524  (func $f (param v128) (param v128) (result v128)
    525    (i8x16.shuffle ${nop8x16_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    526 
    527    assertEq(wasmSimdAnalysis(), "shuffle -> move");
    528 
    529    let mem = new Int8Array(ins.exports.mem.buffer);
    530    set(mem, 16, iota(16));
    531    set(mem, 32, iota(16).map(x => x+16));
    532    ins.exports.run();
    533    assertSame(get(mem, 0, 16), nop8x16_pattern);
    534 }
    535 
    536 // No-op, ignoring left operand, should turn into a move.
    537 {
    538    let nop8x16_pattern = iota(16).map(x => x+16);
    539    let ins = wasmCompile(`
    540 (module
    541  (memory (export "mem") 1 1)
    542  (func (export "run")
    543    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)) (v128.load (i32.const 32)))))
    544  (func $f (param v128) (param v128) (result v128)
    545    (i8x16.shuffle ${nop8x16_pattern.join(' ')} (local.get 0) (local.get 1))))`);
    546 
    547    assertEq(wasmSimdAnalysis(), "shuffle -> move");
    548 
    549    let mem = new Int8Array(ins.exports.mem.buffer);
    550    set(mem, 16, iota(16));
    551    set(mem, 32, iota(16).map(x => x+16));
    552    ins.exports.run();
    553    assertSame(get(mem, 0, 16), nop8x16_pattern);
    554 }
    555 
    556 // Broadcast byte
    557 for ( let byte of [3, 11, 8, 2] ) {
    558    let broadcast8x16_pattern = iota(16).map(_ => byte);
    559    let ins = wasmCompile(`
    560 (module
    561  (memory (export "mem") 1 1)
    562  (func (export "run")
    563    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    564  (func $f (param v128) (result v128)
    565    (i8x16.shuffle ${broadcast8x16_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    566 
    567    assertEq(wasmSimdAnalysis(), "shuffle -> broadcast 8x16");
    568 
    569    let mem = new Int8Array(ins.exports.mem.buffer);
    570    set(mem, 16, iota(16));
    571    ins.exports.run();
    572    assertSame(get(mem, 0, 16), broadcast8x16_pattern);
    573 }
    574 
    575 // Broadcast word from high quadword
    576 {
    577    let broadcast16x8_pattern = [10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11];
    578    let ins = wasmCompile(`
    579 (module
    580  (memory (export "mem") 1 1)
    581  (func (export "run")
    582    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    583  (func $f (param v128) (result v128)
    584    (i8x16.shuffle ${broadcast16x8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    585 
    586    assertEq(wasmSimdAnalysis(), "shuffle -> broadcast 16x8");
    587 
    588    let mem = new Int8Array(ins.exports.mem.buffer);
    589    set(mem, 16, iota(16));
    590    ins.exports.run();
    591    assertSame(get(mem, 0, 16), broadcast16x8_pattern);
    592 }
    593 
    594 // Broadcast word from low quadword
    595 {
    596    let broadcast16x8_pattern = [4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5];
    597    let ins = wasmCompile(`
    598 (module
    599  (memory (export "mem") 1 1)
    600  (func (export "run")
    601    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    602  (func $f (param v128) (result v128)
    603    (i8x16.shuffle ${broadcast16x8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    604 
    605    assertEq(wasmSimdAnalysis(), "shuffle -> broadcast 16x8");
    606 
    607    let mem = new Int8Array(ins.exports.mem.buffer);
    608    set(mem, 16, iota(16));
    609    ins.exports.run();
    610    assertSame(get(mem, 0, 16), broadcast16x8_pattern);
    611 }
    612 
    613 // Broadcast dword from low quadword should turn into a dword permute
    614 {
    615    let broadcast32x4_pattern = [4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7];
    616    let ins = wasmCompile(`
    617 (module
    618  (memory (export "mem") 1 1)
    619  (func (export "run")
    620    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    621  (func $f (param v128) (result v128)
    622    (i8x16.shuffle ${broadcast32x4_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    623 
    624    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
    625 
    626    let mem = new Int8Array(ins.exports.mem.buffer);
    627    set(mem, 16, iota(16));
    628    ins.exports.run();
    629    assertSame(get(mem, 0, 16), broadcast32x4_pattern);
    630 }
    631 
    632 // Broadcast high qword should turn into a dword permute
    633 {
    634    let broadcast64x2_pattern = [8, 9, 10, 11, 12, 13, 14, 15, 8, 9, 10, 11, 12, 13, 14, 15]
    635    let ins = wasmCompile(`
    636 (module
    637  (memory (export "mem") 1 1)
    638  (func (export "run")
    639    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    640  (func $f (param v128) (result v128)
    641    (i8x16.shuffle ${broadcast64x2_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    642 
    643    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
    644 
    645    let mem = new Int8Array(ins.exports.mem.buffer);
    646    set(mem, 16, iota(16));
    647    ins.exports.run();
    648    assertSame(get(mem, 0, 16), broadcast64x2_pattern);
    649 }
    650 
    651 // Byte reversal should be a byte permute
    652 {
    653    let rev8x16_pattern = iota(16).reverse();
    654    let ins = wasmCompile(`
    655 (module
    656  (memory (export "mem") 1 1)
    657  (func (export "run")
    658    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    659  (func $f (param v128) (result v128)
    660    (i8x16.shuffle ${rev8x16_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    661 
    662    assertEq(wasmSimdAnalysis(), "shuffle -> permute 8x16");
    663 
    664    let mem = new Int8Array(ins.exports.mem.buffer);
    665    set(mem, 16, iota(16));
    666    ins.exports.run();
    667    assertSame(get(mem, 0, 16), rev8x16_pattern);
    668 }
    669 
    670 // Byteswap of half-word, word and quad-word groups should be
    671 // reverse bytes analysis
    672 for (let k of [2, 4, 8]) {
    673  let rev8_pattern = iota(16).map(i => i ^ (k - 1));
    674  let ins = wasmCompile(`
    675 (module
    676 (memory (export "mem") 1 1)
    677 (func (export "run")
    678  (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    679 (func $f (param v128) (result v128)
    680  (i8x16.shuffle ${rev8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    681 
    682  assertEq(wasmSimdAnalysis(), `shuffle -> reverse bytes in ${8 * k}-bit lanes`);
    683 
    684  let mem = new Int8Array(ins.exports.mem.buffer);
    685  set(mem, 16, iota(16));
    686  ins.exports.run();
    687  assertSame(get(mem, 0, 16), rev8_pattern);
    688 }
    689 
    690 // Word reversal should be a word permute
    691 {
    692    let rev16x8_pattern = i16ToI8(iota(8).reverse());
    693    let ins = wasmCompile(`
    694 (module
    695  (memory (export "mem") 1 1)
    696  (func (export "run")
    697    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    698  (func $f (param v128) (result v128)
    699    (i8x16.shuffle ${rev16x8_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    700 
    701    assertEq(wasmSimdAnalysis(), "shuffle -> permute 16x8");
    702 
    703    let mem = new Int8Array(ins.exports.mem.buffer);
    704    set(mem, 16, iota(16));
    705    ins.exports.run();
    706    assertSame(get(mem, 0, 16), rev16x8_pattern);
    707 }
    708 
    709 // Dword reversal should be a dword permute
    710 {
    711    let rev32x4_pattern = i32ToI8([3, 2, 1, 0]);
    712    let ins = wasmCompile(`
    713 (module
    714  (memory (export "mem") 1 1)
    715  (func (export "run")
    716    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    717  (func $f (param v128) (result v128)
    718    (i8x16.shuffle ${rev32x4_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    719 
    720    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
    721 
    722    let mem = new Int8Array(ins.exports.mem.buffer);
    723    set(mem, 16, iota(16));
    724    ins.exports.run();
    725    assertSame(get(mem, 0, 16), rev32x4_pattern);
    726 }
    727 
    728 // Qword reversal should be a dword permute
    729 {
    730    let rev64x2_pattern = i32ToI8([2, 3, 0, 1]);
    731    let ins = wasmCompile(`
    732 (module
    733  (memory (export "mem") 1 1)
    734  (func (export "run")
    735    (v128.store (i32.const 0) (call $f (v128.load (i32.const 16)))))
    736  (func $f (param v128) (result v128)
    737    (i8x16.shuffle ${rev64x2_pattern.join(' ')} (local.get 0) (local.get 0))))`);
    738 
    739    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
    740 
    741    let mem = new Int8Array(ins.exports.mem.buffer);
    742    set(mem, 16, iota(16));
    743    ins.exports.run();
    744    assertSame(get(mem, 0, 16), rev64x2_pattern);
    745 }
    746 
    747 // In the case of shifts, we have separate tests that constant shifts work
    748 // correctly, so no such testing is done here.
    749 
    750 for ( let lanes of ['i8x16', 'i16x8', 'i32x4', 'i64x2'] ) {
    751    for ( let shift of ['shl', 'shr_s', 'shr_u'] ) {
    752        for ( let [count, result] of [['(i32.const 5)', /shift -> constant shift/],
    753                                      ['(local.get 1)', /shift -> variable(?: scalarized)? shift/]] ) {
    754            wasmCompile(`(module (func (param v128) (param i32) (result v128) (${lanes}.${shift} (local.get 0) ${count})))`);
    755            assertEq(wasmSimdAnalysis().match(result).length, 1);
    756        }
    757    }
    758 }
    759 
    760 // Zero extending int values.
    761 {
    762  const zeroExtTypes = [
    763    {ty: '8x16', size: 1, ch: 'b'},
    764    {ty: '16x8', size: 2, ch: 'w'},
    765    {ty: '32x4', size: 4, ch: 'd'},
    766    {ty: '64x2', size: 8, ch: 'q'}];
    767  function generateZeroExtend (src, dest, inv) {
    768    const ar = new Array(16);
    769    for (let i = 0, j = 0; i < ar.length; i++) {
    770      if ((i % dest) >= src) {
    771        ar[i] = (inv ? 0 : 16) + (i % 16);
    772        continue;
    773      }
    774      ar[i] = j++ + (inv ? 16 : 0);
    775    }
    776    return ar.join(' ');
    777  }
    778  for (let i = 0; i < 3; i++) {
    779    for (let j = i + 1; j < 4; j++) {
    780      const result = `shuffle -> zero-extend ${zeroExtTypes[i].ty} to ${zeroExtTypes[j].ty}`;
    781      const pat = generateZeroExtend(zeroExtTypes[i].size, zeroExtTypes[j].size, false);
    782      wasmCompile(`(module (func (param v128) (result v128) (i8x16.shuffle ${pat} (local.get 0) (v128.const i32x4 0 0 0 0))))`);
    783      assertEq(wasmSimdAnalysis(), result);
    784 
    785      const patInv = generateZeroExtend(zeroExtTypes[i].size, zeroExtTypes[j].size, true);
    786      wasmCompile(`(module (func (param v128) (result v128) (i8x16.shuffle ${patInv} (v128.const i32x4 0 0 0 0) (local.get 0))))`);
    787      assertEq(wasmSimdAnalysis(), result);
    788 
    789      // Test in wasm by "hidding" zero constant as an argument.
    790      const ins = wasmEvalText(`(module
    791        (func $t (param v128) (result v128) (i8x16.shuffle ${pat} (local.get 0) (v128.const i32x4 0 0 0 0)))
    792        (func $check (param v128) (param v128) (result v128) (i8x16.shuffle ${pat} (local.get 0) (local.get 1)))
    793        (func (export "test") (result i32)
    794          v128.const i32x4 0xff01ee02 0xdd03cc04 0xaa059906 0x88776655
    795          call $t
    796          v128.const i32x4 0xff01ee02 0xdd03cc04 0xaa059906 0x88776655
    797          v128.const i32x4 0 0 0 0
    798          call $check
    799          i8x16.eq
    800          i8x16.bitmask
    801        ))`);
    802      assertEq(ins.exports.test(), 0xffff);
    803    }
    804  }
    805 
    806  // Some patterns that look like zero extend.
    807  for (let pat of ["0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30"]) {
    808    wasmCompile(`(module (func (param v128) (result v128) (i8x16.shuffle ${pat} (local.get 0) (v128.const i32x4 0 0 0 0))))`);
    809    const res = wasmSimdAnalysis();
    810    assertEq(!res.includes("shuffle -> zero-extend"), true);
    811  }
    812 }
    813 
    814 // Constant folding scalar->simd.  There are functional tests for all these in
    815 // ad-hack.js so here we only check that the transformation is triggered.
    816 
    817 for ( let [ty128, ty] of [['i8x16', 'i32'], ['i16x8', 'i32'], ['i32x4', 'i32'],
    818                          ['i64x2', 'i64'], ['f32x4', 'f32'], ['f64x2', 'f64']] )
    819 {
    820    wasmCompile(`(module (func (result v128) (${ty128}.splat (${ty}.const 37))))`);
    821    assertEq(wasmSimdAnalysis(), "scalar-to-simd128 -> constant folded");
    822 }
    823 
    824 // Ditto simd->scalar.
    825 
    826 for ( let [ty128, suffix] of [['i8x16', '_s'], ['i8x16', '_u'], ['i16x8','_s'], ['i16x8','_u'], ['i32x4', '']] ) {
    827    for ( let op of ['any_true', 'all_true', 'bitmask', `extract_lane${suffix} 0`] ) {
    828        let operation = op == 'any_true' ? 'v128.any_true' : `${ty128}.${op}`;
    829        wasmCompile(`(module (func (result i32) (${operation} (v128.const i64x2 0 0))))`);
    830        assertEq(wasmSimdAnalysis(), "simd128-to-scalar -> constant folded");
    831    }
    832 }
    833 
    834 for ( let ty128 of ['f32x4','f64x2','i64x2'] ) {
    835    wasmCompile(`(module (func (result ${ty128.match(/(...)x.*/)[1]}) (${ty128}.extract_lane 0 (v128.const i64x2 0 0))))`);
    836    assertEq(wasmSimdAnalysis(), "simd128-to-scalar -> constant folded");
    837 }
    838 
    839 // Optimizing all_true, any_true, and bitmask that are used for control flow, also when negated.
    840 
    841 for ( let [ty128,size] of [['i8x16',1], ['i16x8',2], ['i32x4',4]] ) {
    842    let all = iota(16/size).map(n => n*n);
    843    let some = iota(16/size).map(n => n*(n % 3));
    844    let none = iota(16/size).map(n => 0);
    845    let inputs = [all, some, none];
    846    let ops = { all_true: allTrue, any_true: anyTrue, bitmask };
    847 
    848    for ( let op of ['any_true', 'all_true', 'bitmask'] ) {
    849        let folded = op != 'bitmask' || (size == 2 && !isArm64);
    850        let operation = op == 'any_true' ? 'v128.any_true' : `${ty128}.${op}`;
    851        let positive =
    852            wasmCompile(
    853                `(module
    854                   (memory (export "mem") 1 1)
    855                   (func $f (param v128) (result i32)
    856                       (if (result i32) (${operation} (local.get 0))
    857                           (then (i32.const 42))
    858                           (else (i32.const 37))))
    859                   (func (export "run") (result i32)
    860                     (call $f (v128.load (i32.const 16)))))`);
    861        assertEq(wasmSimdAnalysis(), folded ? "simd128-to-scalar-and-branch -> folded" : "none");
    862 
    863        let negative =
    864            wasmCompile(
    865                `(module
    866                   (memory (export "mem") 1 1)
    867                   (func $f (param v128) (result i32)
    868                       (if (result i32) (i32.eqz (${operation} (local.get 0)))
    869                           (then (i32.const 42))
    870                           (else (i32.const 37))))
    871                   (func (export "run") (result i32)
    872                     (call $f (v128.load (i32.const 16)))))`);
    873        assertEq(wasmSimdAnalysis(), folded ? "simd128-to-scalar-and-branch -> folded" : "none");
    874 
    875        for ( let inp of inputs ) {
    876            let mem = new this[`Int${8*size}Array`](positive.exports.mem.buffer);
    877            set(mem, 16/size, inp);
    878            assertEq(positive.exports.run(), ops[op](inp) ? 42 : 37);
    879 
    880            mem = new this[`Int${8*size}Array`](negative.exports.mem.buffer);
    881            set(mem, 16/size, inp);
    882            assertEq(negative.exports.run(), ops[op](inp) ? 37 : 42);
    883        }
    884    }
    885 }
    886 
    887 // Constant folding
    888 
    889 {
    890    // Swizzle-with-constant rewritten as shuffle, and then further optimized
    891    // into a dword permute.  Correctness is tested in ad-hack.js.
    892    wasmCompile(`
    893 (module (func (param v128) (result v128)
    894  (i8x16.swizzle (local.get 0) (v128.const i8x16 4 5 6 7 0 1 2 3 12 13 14 15 8 9 10 11))))
    895 `);
    896    assertEq(wasmSimdAnalysis(), "shuffle -> permute 32x4");
    897 }
    898 
    899 // Bitselect with constant mask folded into shuffle operation
    900 
    901 if (!isArm64) {
    902  wasmCompile(`
    903  (module (func (param v128) (param v128) (result v128)
    904    (v128.bitselect (local.get 0) (local.get 1) (v128.const i8x16 0 -1 -1 0 0 0 0 0 -1 -1 -1 -1 -1 -1 0 0))))
    905  `);
    906      assertEq(wasmSimdAnalysis(), "shuffle -> blend 8x16");  
    907 }
    908 
    909 // Library
    910 
    911 function wasmCompile(text) {
    912    return new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(text)))
    913 }
    914 
    915 function get(arr, loc, len) {
    916    let res = [];
    917    for ( let i=0; i < len; i++ ) {
    918        res.push(arr[loc+i]);
    919    }
    920    return res;
    921 }
    922 
    923 function set(arr, loc, vals) {
    924    for ( let i=0; i < vals.length; i++ ) {
    925        arr[loc+i] = vals[i];
    926    }
    927 }
    928 
    929 function i32ToI8(xs) {
    930    return xs.map(x => [x*4, x*4+1, x*4+2, x*4+3]).flat();
    931 }
    932 
    933 function i64ToI2(xs) {
    934  return xs.map(x => [x*8, x*8+1, x*8+2, x*8+3,
    935                      x*8+4, x*8+5, x*8+6, x*8+7]).flat();
    936 }
    937 
    938 function i16ToI8(xs) {
    939    return xs.map(x => [x*2, x*2+1]).flat();
    940 }
    941 
    942 function allTrue(xs) {
    943    return xs.every(v => v != 0);
    944 }
    945 
    946 function anyTrue(xs) {
    947    return xs.some(v => v != 0);
    948 }
    949 
    950 function bitmask(xs) {
    951    let shift = 128/xs.length - 1;
    952    let res = 0;
    953    let k = 0;
    954    xs.forEach(v => { res |= ((v >>> shift) & 1) << k; k++; });
    955    return res;
    956 }