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 }