Skip to content

SIMD tracking issue. #64170

Open

Description

@modulovalue

The following (incomplete, work in progress) tables track our SIMD value types and my progress on them so far. Many things below are aspirational and will need a separate discussion.

If you have a use case you would like to implement with SIMD, or if you run into problems, please open an issue and mention @modulovalue and I will take a look.

Int32x4

Operation API Codegen optimizations Benchmark Issues
splat ✅ cl/531960 ✅ cl/531960, cl/550200 ❓
zero (+ identity) ✅ cl/544840 🚧 ❓
one (* identity) 🚧 🚧 ❓
const constructor 🚧 🚧 ❓ #31487
+ (add) ✅ ✅ cl/497000 ✅ SimdInt32x4
- (subtract) ✅ ✅ cl/497000 ✅ SimdInt32x4
* (multiply) 🚧 cl/551260 🚧 ❓ #59816
unary - (sign negation) ✅ cl/545361 🚧 ❓
& (and) ✅ ✅ cl/497000 ✅ SimdInt32x4, BitArray32x4
| (or) ✅ ✅ cl/497000 ✅ SimdInt32x4, BitArray32x4
^ (xor) ✅ ✅ cl/497000 ✅ SimdInt32x4, BitArray32x4
~ (not) ✅ cl/532480 ✅ cl/532480 TODO extend BitArray32x4
andNot ✅ cl/551000 ✅ cl/558762 TODO extend BitArray32x4
<< (shiftLeft) ✅ cl/545480 ✅ dart2wasm cl/547880, VM cl/553360 ❓ #47114, #63680
>> (shiftRight) ✅ cl/545480 ✅ dart2wasm cl/547880, VM cl/553360 ❓ #47114, #63680
>>> (shiftRightUnsigned) ✅ cl/557620 ✅ dart2wasm cl/557620, VM cl/560562 ❓ #64345
equal ✅ cl/528620 ✅ cl/539060, cl/539520 ✅ SimdSearch
notEqual ✅ cl/541221 ✅ cl/541240 TODO extend SimdSearch
< (lessThan) ✅ cl/543500 🚧 dart2wasm ✅ cl/547880, VM in progress ❓ #63680
<= (lessThanOrEqual) ✅ cl/543500 🚧 dart2wasm ✅ cl/547880, VM in progress ❓ #63680
> (greaterThan) ✅ cl/543500 🚧 dart2wasm ✅ cl/547880, VM in progress ❓ #63680
>= (greaterThanOrEqual) ✅ cl/543500 🚧 dart2wasm ✅ cl/547880, VM in progress ❓ #63680
min ✅ cl/551040 🚧 dart2wasm ✅ cl/547880, VM in progress ❓
max ✅ cl/551040 🚧 dart2wasm ✅ cl/547880, VM in progress ❓
abs ✅ cl/545361 🚧 dart2wasm ✅ cl/547880, VM pending ❓
anyTrue ✅ cl/528620 ✅ cl/539400 ✅ SimdSearch #64129
allTrue ✅ cl/543480 ✅ cl/551641 ❓ #64129
signMask ✅ ✅ cl/521245, cl/542362, cl/542363 ✅ SimdInt32x4 #63680
x/y/z/w ✅ ✅ cl/507761 ✅ BitArray32x4
flagX/Y/Z/W ✅ ✅ ❓ #64129
withX/Y/Z/W ✅ ✅ cl/550201 ❓
withFlagX/Y/Z/W ✅ ✅ ❓ #64129
shuffle ✅ ✅ cl/559002, arm64 cl/562680, cl/562681, cl/563880, cl/563881 ❓ #63680
shuffleMix ✅ ✅ cl/559002, arm64 cl/562680, cl/562681, cl/563880, cl/563881 ❓
swizzle 🚧 🚧 ❓
select ✅ ✅ ❓
== / hashCode 🚧 in progress 🚧 in progress ❓ #43255
toString ✅ cl/547261 ✅ cl/547261 ❓ #63847
conversions (fromFloat32x4Bits) 🚧 ❓ ❓ #46182

Float32x4

Operation API Codegen optimizations Benchmark Issues
splat ✅ ✅ ❓
zero (+ identity) ✅ ✅ ❓
one (* identity) 🚧 🚧 ❓
const constructor 🚧 🚧 ❓ #31487, #56363
+ (add) ✅ ✅ ❓
- (subtract) ✅ ✅ ❓
* (multiply) ✅ ✅ ❓
/ (divide) ✅ ✅ ❓
unary - (sign negation) ✅ ✅ ❓
abs ✅ ✅ ❓
min ✅ ✅ cl/543520, cl/562661 ❓ #63962, #64521
max ✅ ✅ cl/543520, cl/562661 ❓ #63962, #64521
sqrt ✅ ✅ ❓
reciprocal ✅ ✅ ❓ #39551
reciprocalSqrt ✅ ✅ ❓ #39551
scale ✅ ✅ ❓
clamp ✅ ✅ cl/549060 ❓ #40426
ceil 🚧 🚧 ❓
floor 🚧 🚧 ❓
trunc 🚧 🚧 ❓
nearest 🚧 🚧 ❓ #61240
pmin 🚧 🚧 ❓
pmax 🚧 🚧 ❓
equal ✅ ✅ ❓
notEqual ✅ ✅ ❓
< (lessThan) ✅ ✅ ❓
<= (lessThanOrEqual) ✅ ✅ ❓
> (greaterThan) ✅ ✅ ❓
>= (greaterThanOrEqual) ✅ ✅ ❓
signMask ✅ ✅ cl/542362, cl/542363 ❓
x/y/z/w ✅ ✅ ❓
withX/Y/Z/W ✅ ✅ ❓
shuffle ✅ ✅ cl/559002, arm64 cl/562680, cl/562681, cl/563880, cl/563881 ❓
shuffleMix ✅ ✅ cl/559002, arm64 cl/562680, cl/562681, cl/563880, cl/563881 ❓
== / hashCode 🚧 in progress 🚧 in progress ❓ #43255
toString ✅ cl/547261 ✅ cl/547261 ❓ #63847
conversions (fromInt32x4Bits, fromFloat64x2) 🚧 ❓ ❓ #46182

Float64x2

Operation API Codegen optimizations Benchmark Issues
splat ✅ ✅ ❓
zero (+ identity) ✅ ✅ ❓
one (* identity) 🚧 🚧 ❓
const constructor 🚧 🚧 ❓ #31487
+ (add) ✅ ✅ ❓
- (subtract) ✅ ✅ ❓
* (multiply) ✅ ✅ ❓
/ (divide) ✅ ✅ ❓
unary - (sign negation) ✅ ✅ ❓
abs ✅ ✅ ❓
min ✅ ✅ cl/543520, cl/562661 ❓ #63962, #31048, #64521
max ✅ ✅ cl/543520, cl/562661 ❓ #63962, #31048, #64521
sqrt ✅ ✅ ❓
scale ✅ ✅ ❓
clamp ✅ ✅ cl/549060 ❓ #40426
ceil 🚧 🚧 ❓
floor 🚧 🚧 ❓
trunc 🚧 🚧 ❓
nearest 🚧 🚧 ❓ #61240
pmin 🚧 🚧 ❓
pmax 🚧 🚧 ❓
equal 🚧 🚧 ❓
notEqual 🚧 🚧 ❓
< (lessThan) 🚧 🚧 ❓
<= (lessThanOrEqual) 🚧 🚧 ❓
> (greaterThan) 🚧 🚧 ❓
>= (greaterThanOrEqual) 🚧 🚧 ❓
signMask ✅ ✅ cl/542363 ❓
x/y ✅ ✅ ❓
withX/Y ✅ ✅ ❓
== / hashCode 🚧 in progress 🚧 in progress ❓ #43255
toString ✅ cl/547261 ✅ cl/547261 ❓ #63847
conversions (fromFloat32x4) 🚧 ❓ ❓ #46182

Int8x16 / Uint8x16, Int16x8 / Uint16x8, Uint32x4, Int64x2

They are on my todo list. We don't have types for those yet (#63471).

The wasm level groundwork has landed: WasmI8x16 cl/551701, WasmI16x8 cl/551702, WasmI64x2 cl/551700.

🚧 In progress: a Uint8x16 value type on the VM (unboxing, arithmetic, equal, anyTrue), not uploaded yet.

Priorities

Because there's a lot to do, I decided to focus on what's most important, and deprioritize certain parts:

  • pmin/pmax & relaxed_min/relaxed_max: pmin/pmax benefits x86 and hurts arm64. relaxed_min/relaxed_max restores performance on arm64 to min/max levels. Since float64 and float32 use cases are most relevant for Flutter, where most of our users are on arm64, min and max is already optimal for most of our users.
  • x86 would benefit from commutativity exposed in TFA for better code generation.
  • operations that are not part of the Wasm SIMD API surface (e.g., rotateRight/rotateLeft) will be investigated later.

Third-party blockers

Use cases

Activity

  1. added theissue type on Aug 30, 2026
  2. added
    area-sdkUse area-sdk for general purpose SDK issues (packaging, distribution, …).
    on Aug 31, 2026
  3. krll-kov commented on Sep 4, 2026

    @krll-kov

    @modulovalue

    A use case, since the issue asks for them. I'm porting libwebp's VP8 encoder to Dart. The inner kernels are a typical 8-bit image workload: forward and inverse 4x4 DCT, a Hadamard distortion metric, sums of squared differences, and a quantizer. All of them read Uint8List at a fixed row stride, and libwebp has NEON and SSE versions of all of them.

    Here is what I ran into trying to vectorize them with dart compile exe on macos_arm64.

    Arithmetic over a populated Float32x4List is fine, 2.5x faster than the same scalar loop. The problem is getting byte data into the lanes. Each lane has to be filled separately, and the int to double conversion is about 5x slower in AOT than in JIT. That alone made a vectorized 16x16 SSD 3x slower than the scalar version. I filed that as #64209. Writing the conversion as 0.0 + x instead of x.toDouble() avoids it, and then the same kernel is 14% faster than scalar.

    With that out of the way, what still blocks the rest:

    • Int32x4 has no * (Support multiplication on Int32x4 #59816), so an integer kernel can't stay in integer lanes. The DCT multiplies by 2217, 5352, 20091 and 35468, and the quantizer multiplies by a reciprocal, so both need it.
    • Float32x4 can't cover for that. It's only exact up to 2^24, and the second pass of the DCT reaches about 8.7e7 while the quantizer's coeff * iq reaches about 1e9. Only the Hadamard metric and the SSD stay in the exact range.
    • An 8-bit lane type ([SIMD] Support for Int8x16 and Int8x16List. #63471) would suit this workload much better than widening bytes to float. A 16-byte row would load in one read instead of sixteen byte reads and four vector constructions, and that path is where the time goes on small blocks. A 16-bit lane type would be even more useful for the transforms, since the intermediates outgrow 8 bits right away.

    So right now SIMD pays off in one kernel out of five and loses in the others.

  4. 34 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area-sdkUse area-sdk for general purpose SDK issues (packaging, distribution, …).library-typed-data

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions