Skip to content

Commit 8eb745f

Browse files
committed
[JSC] Implement SIMD Shuffle reduction
https://bugs.webkit.org/show_bug.cgi?id=309764 rdar://172351912 Reviewed by Keith Miller. This patch implements recently enabled V8's SIMD shuffle optimization. 1. Add SIMD reductions ARM64 has zip1, zip2, uzp1, uzp2, trn1, trn2, ext, rev16, rev32, rev64 instructions. And they are more specific shuffling than using tbl instruction, and it is cheaper than that. We do strength-reduction for the input to optimize it. However if we do it too early, it makes analysis of Shuffle hard. Thus we introduce two passes to B3ReduceStrength. And we do this Vector shuffle reduction only in the latter pass. We also add several simple reductions which can be seen in the JetStream3/argon2-wasm, Vector left-shift to VectorAdd. Using VectorDupElement. 2. Add B3SIMDShuffleReduction phase This phase analyzes VectorSwizzle of VectorSwizzle, and attempt to remove it by reconstructing the pattern for the final output. 3. ARM64 SHA3 XAR lowering Particular pattern can be converted into ARM64 SHA3-feature's XAR instruction. We add a lowering rule for that when ARM64 SHA3-feature is available. Tests: JSTests/wasm/stress/simd-canonical-shuffle.js JSTests/wasm/stress/simd-ext-pattern.js JSTests/wasm/stress/simd-shift-left-one.js JSTests/wasm/stress/simd-shuffle-compose.js JSTests/wasm/stress/simd-shuffle-narrowing.js JSTests/wasm/stress/simd-unary-shuffle-canonical.js JSTests/wasm/stress/simd-xor-rotate.js Source/JavaScriptCore/b3/testb3_1.cpp Source/JavaScriptCore/b3/testb3_7.cpp * JSTests/wasm/stress/simd-canonical-shuffle.js: Added. (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.const.mem.new.DataView.setI32x4): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.getI32x4): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.setBytes): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.getBytes): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.verify): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test): * JSTests/wasm/stress/simd-ext-pattern.js: Added. (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.getResult): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test): * JSTests/wasm/stress/simd-shift-left-one.js: Added. (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test): * JSTests/wasm/stress/simd-shuffle-compose.js: Added. (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.getResult): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test): * JSTests/wasm/stress/simd-shuffle-narrowing.js: Added. (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test): * JSTests/wasm/stress/simd-unary-shuffle-canonical.js: Added. (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.getResult32): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test): * JSTests/wasm/stress/simd-xor-rotate.js: Added. (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.rotr64): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test.let.xor0.0x0123456789ABCDEFn.0xFF00FF00FF00FF00n.let.xor1.0xFEDCBA9876543210n.0x00FF00FF00FF00FFn.verify): (from.string_appeared_here.import.as.assert.from.string_appeared_here.async test): * Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj: * Source/JavaScriptCore/Sources.txt: * Source/JavaScriptCore/assembler/ARM64Assembler.h: * Source/JavaScriptCore/assembler/CPU.cpp: (JSC::isARM64_SHA3): * Source/JavaScriptCore/assembler/CPU.h: (JSC::isARM64_SHA3): * Source/JavaScriptCore/assembler/MacroAssemblerARM64.cpp: (JSC::MacroAssemblerARM64::collectCPUFeatures): * Source/JavaScriptCore/assembler/MacroAssemblerARM64.h: (JSC::MacroAssemblerARM64::supportsSHA3): (JSC::MacroAssemblerARM64::vectorXorRotateRight64): (JSC::MacroAssemblerARM64::vectorZipLower): (JSC::MacroAssemblerARM64::vectorZipHigher): (JSC::MacroAssemblerARM64::vectorUnzipOdd): (JSC::MacroAssemblerARM64::vectorTransposeEven): (JSC::MacroAssemblerARM64::vectorTransposeOdd): (JSC::MacroAssemblerARM64::vectorReverse): (JSC::MacroAssemblerARM64::vectorZipUpper): Deleted. * Source/JavaScriptCore/b3/B3Generate.cpp: (JSC::B3::generateToAir): * Source/JavaScriptCore/b3/B3LowerToAir.cpp: * Source/JavaScriptCore/b3/B3Opcode.h: * Source/JavaScriptCore/b3/B3ReduceSIMDShuffle.cpp: Added. (JSC::B3::reduceSIMDShuffle): * Source/JavaScriptCore/b3/B3ReduceSIMDShuffle.h: Copied from Source/JavaScriptCore/b3/B3ReduceStrength.h. * Source/JavaScriptCore/b3/B3ReduceStrength.cpp: (JSC::B3::reduceStrength): * Source/JavaScriptCore/b3/B3ReduceStrength.h: * Source/JavaScriptCore/b3/B3SIMDValue.h: * Source/JavaScriptCore/b3/B3UseCounts.cpp: (JSC::B3::UseCountsWithoutUsingInstructions::UseCountsWithoutUsingInstructions): * Source/JavaScriptCore/b3/B3UseCounts.h: (JSC::B3::UseCountsWithoutUsingInstructions::numUses const): * Source/JavaScriptCore/b3/B3Validate.cpp: * Source/JavaScriptCore/b3/B3Value.cpp: (JSC::B3::Value::effects const): (JSC::B3::Value::key const): * Source/JavaScriptCore/b3/B3Value.h: * Source/JavaScriptCore/b3/B3ValueInlines.h: * Source/JavaScriptCore/b3/B3ValueKey.cpp: (JSC::B3::ValueKey::materialize const): * Source/JavaScriptCore/b3/air/AirLowerMacros.cpp: (JSC::B3::Air::lowerMacros): * Source/JavaScriptCore/b3/air/AirOpcode.opcodes: * Source/JavaScriptCore/b3/air/opcode_generator.rb: * Source/JavaScriptCore/b3/testb3.h: * Source/JavaScriptCore/b3/testb3_1.cpp: (run): * Source/JavaScriptCore/b3/testb3_7.cpp: (testVectorXorRotateRight64): (testVectorUnzipEven): (testVectorUnzipOdd): (testVectorZipLower): (testVectorZipHigher): (testVectorTransposeEven): (testVectorTransposeOdd): (testVectorReverse): (testVectorShiftByVectorShlByOne): (testBinarySwizzlePattern): (testUnarySwizzlePattern): (testVectorSwizzleToUnzipEven): (testVectorSwizzleBinaryToUnzipOdd): (testVectorExtractPair): (testVectorSwizzleBinaryToEXT): (testVectorSwizzleUnaryToEXT): (testVectorSwizzleBinaryCanonical): (testVectorSwizzleUnaryCanonical): (testVectorCanonicalSameInputFolding): (testVectorSwizzleToDupElement): (testVectorSwizzleComposition): * Source/JavaScriptCore/jit/SIMDInfo.h: * Source/JavaScriptCore/jit/SIMDShuffle.h: (JSC::SIMDShuffle::isI8x16SameElement): (JSC::SIMDShuffle::isEXTWithSwap): (JSC::SIMDShuffle::tryMatchCanonicalBinary): (JSC::SIMDShuffle::tryMatchUnaryAsBinaryCanonical): (JSC::SIMDShuffle::tryMatchCanonicalUnary): (JSC::SIMDShuffle::composeShuffle): (JSC::SIMDShuffle::tryMatchCanonicalBinaryImpl): * Source/JavaScriptCore/runtime/OptionsList.h: * Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp: (JSC::Wasm::BBQJITImpl::BBQJIT::addSIMDI_V): * Source/WTF/wtf/PlatformHave.h: Canonical link: https://commits.webkit.org/309250@main
1 parent 372cd42 commit 8eb745f

40 files changed

Lines changed: 3083 additions & 37 deletions
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//@ requireOptions("--useWasmSIMD=1")
2+
//@ skip if !$isSIMDPlatform
3+
import { instantiate } from "../wabt-wrapper.js"
4+
import * as assert from "../assert.js"
5+
6+
// Comprehensive test for binary shuffle canonical pattern recognition.
7+
// These patterns should be lowered to UZP1/UZP2/ZIP1/ZIP2/TRN1/TRN2/REV instructions.
8+
9+
async function test() {
10+
let wat = `(module
11+
(memory (export "mem") 1)
12+
13+
;; UZP1.4S binary: {a[0],a[2],b[0],b[2]}
14+
(func (export "uzp1_4s") (param i32 i32 i32)
15+
(v128.store (local.get 2)
16+
(i8x16.shuffle 0 1 2 3 8 9 10 11 16 17 18 19 24 25 26 27
17+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
18+
19+
;; UZP2.4S binary: {a[1],a[3],b[1],b[3]}
20+
(func (export "uzp2_4s") (param i32 i32 i32)
21+
(v128.store (local.get 2)
22+
(i8x16.shuffle 4 5 6 7 12 13 14 15 20 21 22 23 28 29 30 31
23+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
24+
25+
;; ZIP1.4S binary: {a[0],b[0],a[1],b[1]}
26+
(func (export "zip1_4s") (param i32 i32 i32)
27+
(v128.store (local.get 2)
28+
(i8x16.shuffle 0 1 2 3 16 17 18 19 4 5 6 7 20 21 22 23
29+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
30+
31+
;; ZIP2.4S binary: {a[2],b[2],a[3],b[3]}
32+
(func (export "zip2_4s") (param i32 i32 i32)
33+
(v128.store (local.get 2)
34+
(i8x16.shuffle 8 9 10 11 24 25 26 27 12 13 14 15 28 29 30 31
35+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
36+
37+
;; TRN1.4S binary: {a[0],b[0],a[2],b[2]}
38+
(func (export "trn1_4s") (param i32 i32 i32)
39+
(v128.store (local.get 2)
40+
(i8x16.shuffle 0 1 2 3 16 17 18 19 8 9 10 11 24 25 26 27
41+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
42+
43+
;; TRN2.4S binary: {a[1],b[1],a[3],b[3]}
44+
(func (export "trn2_4s") (param i32 i32 i32)
45+
(v128.store (local.get 2)
46+
(i8x16.shuffle 4 5 6 7 20 21 22 23 12 13 14 15 28 29 30 31
47+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
48+
49+
;; UZP1.2D binary: {a[lo64],b[lo64]}
50+
(func (export "uzp1_2d") (param i32 i32 i32)
51+
(v128.store (local.get 2)
52+
(i8x16.shuffle 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
53+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
54+
55+
;; UZP2.2D binary: {a[hi64],b[hi64]}
56+
(func (export "uzp2_2d") (param i32 i32 i32)
57+
(v128.store (local.get 2)
58+
(i8x16.shuffle 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
59+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
60+
61+
;; Unary REV64.4S: swap 32-bit pairs within 64-bit lanes
62+
(func (export "rev64_4s_unary") (param i32 i32)
63+
(v128.store (local.get 1)
64+
(i8x16.shuffle 4 5 6 7 0 1 2 3 12 13 14 15 8 9 10 11
65+
(v128.load (local.get 0))
66+
(v128.load (local.get 0)))))
67+
68+
;; Unary REV32.8H: swap 16-bit elements within 32-bit groups
69+
(func (export "rev32_8h_unary") (param i32 i32)
70+
(v128.store (local.get 1)
71+
(i8x16.shuffle 2 3 0 1 6 7 4 5 10 11 8 9 14 15 12 13
72+
(v128.load (local.get 0))
73+
(v128.load (local.get 0)))))
74+
75+
;; Unary REV16.16B: swap bytes within 16-bit groups
76+
(func (export "rev16_16b_unary") (param i32 i32)
77+
(v128.store (local.get 1)
78+
(i8x16.shuffle 1 0 3 2 5 4 7 6 9 8 11 10 13 12 15 14
79+
(v128.load (local.get 0))
80+
(v128.load (local.get 0)))))
81+
82+
;; Unary UZP1.4S(v,v): {v[0],v[2],v[0],v[2]}
83+
(func (export "uzp1_4s_unary") (param i32 i32)
84+
(v128.store (local.get 1)
85+
(i8x16.shuffle 0 1 2 3 8 9 10 11 0 1 2 3 8 9 10 11
86+
(v128.load (local.get 0))
87+
(v128.load (local.get 0)))))
88+
89+
;; Unary TRN1.4S(v,v): {v[0],v[0],v[2],v[2]}
90+
(func (export "trn1_4s_unary") (param i32 i32)
91+
(v128.store (local.get 1)
92+
(i8x16.shuffle 0 1 2 3 0 1 2 3 8 9 10 11 8 9 10 11
93+
(v128.load (local.get 0))
94+
(v128.load (local.get 0)))))
95+
)`
96+
97+
const instance = await instantiate(wat, {}, { simd: true })
98+
const mem = new DataView(instance.exports.mem.buffer)
99+
100+
function setI32x4(offset, a, b, c, d) {
101+
mem.setUint32(offset, a, true)
102+
mem.setUint32(offset+4, b, true)
103+
mem.setUint32(offset+8, c, true)
104+
mem.setUint32(offset+12, d, true)
105+
}
106+
function getI32x4(offset) {
107+
return [mem.getUint32(offset, true), mem.getUint32(offset+4, true),
108+
mem.getUint32(offset+8, true), mem.getUint32(offset+12, true)]
109+
}
110+
function setBytes(offset, arr) {
111+
const u8 = new Uint8Array(instance.exports.mem.buffer)
112+
for (let i = 0; i < arr.length; i++) u8[offset+i] = arr[i]
113+
}
114+
function getBytes(offset, len) {
115+
const u8 = new Uint8Array(instance.exports.mem.buffer)
116+
return Array.from(u8.slice(offset, offset+len))
117+
}
118+
119+
// Setup: a = {0x11, 0x22, 0x33, 0x44}, b = {0x55, 0x66, 0x77, 0x88}
120+
setI32x4(0, 0x11, 0x22, 0x33, 0x44)
121+
setI32x4(16, 0x55, 0x66, 0x77, 0x88)
122+
123+
function verify(name, fn, args, expected) {
124+
fn(...args)
125+
let got = getI32x4(args[args.length-1])
126+
assert.eq(got.join(','), expected.join(','), name)
127+
}
128+
129+
// Binary patterns
130+
verify("UZP1.4S", instance.exports.uzp1_4s, [0,16,32], [0x11,0x33,0x55,0x77])
131+
verify("UZP2.4S", instance.exports.uzp2_4s, [0,16,32], [0x22,0x44,0x66,0x88])
132+
verify("ZIP1.4S", instance.exports.zip1_4s, [0,16,32], [0x11,0x55,0x22,0x66])
133+
verify("ZIP2.4S", instance.exports.zip2_4s, [0,16,32], [0x33,0x77,0x44,0x88])
134+
verify("TRN1.4S", instance.exports.trn1_4s, [0,16,32], [0x11,0x55,0x33,0x77])
135+
verify("TRN2.4S", instance.exports.trn2_4s, [0,16,32], [0x22,0x66,0x44,0x88])
136+
verify("UZP1.2D", instance.exports.uzp1_2d, [0,16,32], [0x11,0x22,0x55,0x66])
137+
verify("UZP2.2D", instance.exports.uzp2_2d, [0,16,32], [0x33,0x44,0x77,0x88])
138+
139+
// Unary patterns
140+
verify("REV64.4S unary", instance.exports.rev64_4s_unary, [0,32], [0x22,0x11,0x44,0x33])
141+
verify("UZP1.4S unary", instance.exports.uzp1_4s_unary, [0,32], [0x11,0x33,0x11,0x33])
142+
verify("TRN1.4S unary", instance.exports.trn1_4s_unary, [0,32], [0x11,0x11,0x33,0x33])
143+
144+
// Byte-level tests
145+
setBytes(0, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
146+
instance.exports.rev32_8h_unary(0, 32)
147+
assert.eq(getBytes(32, 16).join(','), '2,3,0,1,6,7,4,5,10,11,8,9,14,15,12,13', 'REV32.8H')
148+
149+
instance.exports.rev16_16b_unary(0, 32)
150+
assert.eq(getBytes(32, 16).join(','), '1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14', 'REV16.16B')
151+
152+
// Trigger OMG and re-verify
153+
for (let i = 0; i < wasmTestLoopCount; ++i) {
154+
instance.exports.uzp1_4s(0, 16, 32)
155+
instance.exports.uzp2_4s(0, 16, 32)
156+
instance.exports.zip1_4s(0, 16, 32)
157+
instance.exports.zip2_4s(0, 16, 32)
158+
instance.exports.trn1_4s(0, 16, 32)
159+
instance.exports.trn2_4s(0, 16, 32)
160+
instance.exports.rev64_4s_unary(0, 32)
161+
instance.exports.rev32_8h_unary(0, 32)
162+
instance.exports.rev16_16b_unary(0, 32)
163+
instance.exports.uzp1_4s_unary(0, 32)
164+
instance.exports.trn1_4s_unary(0, 32)
165+
}
166+
167+
// Re-verify after OMG
168+
setI32x4(0, 0x11, 0x22, 0x33, 0x44)
169+
setI32x4(16, 0x55, 0x66, 0x77, 0x88)
170+
verify("UZP1.4S post-OMG", instance.exports.uzp1_4s, [0,16,32], [0x11,0x33,0x55,0x77])
171+
verify("ZIP1.4S post-OMG", instance.exports.zip1_4s, [0,16,32], [0x11,0x55,0x22,0x66])
172+
verify("TRN1.4S post-OMG", instance.exports.trn1_4s, [0,16,32], [0x11,0x55,0x33,0x77])
173+
verify("REV64.4S post-OMG", instance.exports.rev64_4s_unary, [0,32], [0x22,0x11,0x44,0x33])
174+
175+
setBytes(0, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
176+
instance.exports.rev16_16b_unary(0, 32)
177+
assert.eq(getBytes(32, 16).join(','), '1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14', 'REV16.16B post-OMG')
178+
}
179+
180+
await assert.asyncTest(test())
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//@ requireOptions("--useWasmSIMD=1")
2+
//@ skip if !$isSIMDPlatform
3+
import { instantiate } from "../wabt-wrapper.js"
4+
import * as assert from "../assert.js"
5+
6+
// Test binary shuffle patterns recognized as EXT, UZP, ZIP instructions.
7+
8+
async function test() {
9+
let wat = `(module
10+
(memory (export "mem") 1)
11+
12+
;; EXT #8: bytes {8,...,15, 16,...,23}
13+
(func (export "ext8") (param i32 i32)
14+
(v128.store (i32.const 32)
15+
(i8x16.shuffle 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
16+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
17+
18+
;; EXT #4
19+
(func (export "ext4") (param i32 i32)
20+
(v128.store (i32.const 32)
21+
(i8x16.shuffle 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
22+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
23+
24+
;; UZP1.2D: {a[0..7], b[0..7]}
25+
(func (export "uzp1_64") (param i32 i32)
26+
(v128.store (i32.const 32)
27+
(i8x16.shuffle 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
28+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
29+
30+
;; UZP2.2D: {a[8..15], b[8..15]}
31+
(func (export "uzp2_64") (param i32 i32)
32+
(v128.store (i32.const 32)
33+
(i8x16.shuffle 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
34+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
35+
36+
;; ZIP1.4S: {a[0],b[0],a[1],b[1]}
37+
(func (export "zip1_32") (param i32 i32)
38+
(v128.store (i32.const 32)
39+
(i8x16.shuffle 0 1 2 3 16 17 18 19 4 5 6 7 20 21 22 23
40+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
41+
42+
;; ZIP2.4S: {a[2],b[2],a[3],b[3]}
43+
(func (export "zip2_32") (param i32 i32)
44+
(v128.store (i32.const 32)
45+
(i8x16.shuffle 8 9 10 11 24 25 26 27 12 13 14 15 28 29 30 31
46+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
47+
48+
;; UZP1.4S: {a[0],a[2],b[0],b[2]}
49+
(func (export "uzp1_32") (param i32 i32)
50+
(v128.store (i32.const 32)
51+
(i8x16.shuffle 0 1 2 3 8 9 10 11 16 17 18 19 24 25 26 27
52+
(v128.load (local.get 0)) (v128.load (local.get 1)))))
53+
)`
54+
55+
const instance = await instantiate(wat, {}, { simd: true })
56+
const mem = new Uint8Array(instance.exports.mem.buffer)
57+
58+
// Set up test vectors: a = [0..15], b = [16..31]
59+
for (let i = 0; i < 16; i++) {
60+
mem[i] = i
61+
mem[16 + i] = 16 + i
62+
}
63+
64+
function getResult() {
65+
return Array.from(mem.slice(32, 48)).join(',')
66+
}
67+
68+
// EXT #8
69+
instance.exports.ext8(0, 16)
70+
assert.eq(getResult(), '8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23')
71+
72+
// EXT #4
73+
instance.exports.ext4(0, 16)
74+
assert.eq(getResult(), '4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19')
75+
76+
// UZP1.2D
77+
instance.exports.uzp1_64(0, 16)
78+
assert.eq(getResult(), '0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23')
79+
80+
// UZP2.2D
81+
instance.exports.uzp2_64(0, 16)
82+
assert.eq(getResult(), '8,9,10,11,12,13,14,15,24,25,26,27,28,29,30,31')
83+
84+
// ZIP1.4S
85+
instance.exports.zip1_32(0, 16)
86+
assert.eq(getResult(), '0,1,2,3,16,17,18,19,4,5,6,7,20,21,22,23')
87+
88+
// ZIP2.4S
89+
instance.exports.zip2_32(0, 16)
90+
assert.eq(getResult(), '8,9,10,11,24,25,26,27,12,13,14,15,28,29,30,31')
91+
92+
// UZP1.4S
93+
instance.exports.uzp1_32(0, 16)
94+
assert.eq(getResult(), '0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27')
95+
96+
// Run many times for OMG
97+
for (let i = 0; i < wasmTestLoopCount; ++i) {
98+
instance.exports.ext8(0, 16)
99+
instance.exports.uzp1_64(0, 16)
100+
instance.exports.zip1_32(0, 16)
101+
}
102+
103+
// Re-verify after optimization
104+
instance.exports.ext8(0, 16)
105+
assert.eq(getResult(), '8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23')
106+
}
107+
108+
await assert.asyncTest(test())
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//@ requireOptions("--useWasmSIMD=1")
2+
//@ skip if !$isSIMDPlatform
3+
import { instantiate } from "../wabt-wrapper.js"
4+
import * as assert from "../assert.js"
5+
6+
// Test that i64x2.shl by constant 1 is optimized to i64x2.add(x, x).
7+
// This is a strength reduction: x << 1 == x + x.
8+
9+
async function test() {
10+
let wat = `(module
11+
(memory (export "mem") 1)
12+
13+
;; Simple shift left by 1
14+
(func (export "shl1") (param i32 i32)
15+
(v128.store (local.get 1)
16+
(i64x2.shl (v128.load (local.get 0)) (i32.const 1))))
17+
18+
;; Multiply-and-double pattern (common in BLAKE2b):
19+
;; (lo32(a) * lo32(b)) << 1
20+
(func (export "mul_double") (param i32 i32 i32)
21+
(v128.store (local.get 2)
22+
(i64x2.shl
23+
(i64x2.mul (v128.load (local.get 0)) (v128.load (local.get 1)))
24+
(i32.const 1))))
25+
)`
26+
27+
const instance = await instantiate(wat, {}, { simd: true })
28+
const mem = new DataView(instance.exports.mem.buffer)
29+
30+
// Test shl1: [3, 7] << 1 = [6, 14]
31+
mem.setBigUint64(0, 3n, true)
32+
mem.setBigUint64(8, 7n, true)
33+
instance.exports.shl1(0, 16)
34+
assert.eq(mem.getBigUint64(16, true), 6n)
35+
assert.eq(mem.getBigUint64(24, true), 14n)
36+
37+
// Test with large values near overflow
38+
mem.setBigUint64(0, 0x8000000000000000n, true)
39+
mem.setBigUint64(8, 0xFFFFFFFFFFFFFFFFn, true)
40+
instance.exports.shl1(0, 16)
41+
assert.eq(mem.getBigUint64(16, true), 0n) // overflow wraps
42+
assert.eq(mem.getBigUint64(24, true), 0xFFFFFFFFFFFFFFFEn)
43+
44+
// Test mul_double: [5, 3] * [4, 6] << 1 = [40, 36]
45+
mem.setBigUint64(0, 5n, true)
46+
mem.setBigUint64(8, 3n, true)
47+
mem.setBigUint64(16, 4n, true)
48+
mem.setBigUint64(24, 6n, true)
49+
instance.exports.mul_double(0, 16, 32)
50+
assert.eq(mem.getBigUint64(32, true), 40n)
51+
assert.eq(mem.getBigUint64(40, true), 36n)
52+
53+
// Run many times to trigger OMG
54+
for (let i = 0; i < wasmTestLoopCount; ++i) {
55+
instance.exports.shl1(0, 16)
56+
instance.exports.mul_double(0, 16, 32)
57+
}
58+
59+
// Re-verify after OMG
60+
mem.setBigUint64(0, 3n, true)
61+
mem.setBigUint64(8, 7n, true)
62+
instance.exports.shl1(0, 16)
63+
assert.eq(mem.getBigUint64(16, true), 6n)
64+
assert.eq(mem.getBigUint64(24, true), 14n)
65+
}
66+
67+
await assert.asyncTest(test())

0 commit comments

Comments
 (0)