Repository navigation
[SIMD] Support for Int8x16 and Int8x16List. #63471
Description
I'd like to start working on Int8x16 and Int8x16List so we can bring byte-level SIMD support to Dart.
The plan is:
-
Our Int32x4 implementation provides the blueprint for Int8x16 so I expect that implementing an Int8x16 type should mostly be a mechanical change that mirrors Int32x4.
-
Since our semantics make hoisting non-constant global values difficult (see: [vm] Failure to hoist certain static loads. #63390) I'd also like to try to make the Int8x16 constructor constant. Constant
Symbolconstructors provide the precedent. The change should be mechanical mirroring of Symbol. -
Initial op set will be the minimum needed for indexOf. I have a prototype branch showing ~100x over Dart's current
OneByteString.indexOf.
Goals:
- Support Int8x16 and Int8x16List and dogfood it somewhere in the SDK. I think OneByteString.indexOf is probably the best use case that is simple enough and valuable enough to be both user-visible and benchmark-quantifiable.
- Support accelerated byte-level SIMD operations on Wasm, x86 (gates on SSE availability) and arm64 & arm32 (gated on NEON availability).
Non goals:
- js doesn't support SIMD operations. Using Wasm is not an option since crossing the js-wasm barrier introduces too much overhead, so Dart fallbacks will be used there and documentation will note that dart2wasm is the recommended target for SIMD performance.
- riscv32 and riscv64 use fundamentally different SIMD instruction sets. To reduce the scope, Dart fallback implementations will be used on riscv targets. (some accelerated riscv ops may end up being added on demand if dogfooding requires them, but full parity with x86 and arm is explicitly not promised in this phase)
This will be the first step towards unblocking implementing more efficient json decoders (see: #35693) directly in Dart by eventually giving us the ability to use simdjson techniques.
I plan to continue adding support for more operations to Int8x16 driven by parsing-related use cases if this experiment goes well.
cc @mraleph @lrhn @kevmoo @rakudrama thoughts?
Activity
kevmoo commentedon May 28, 2026
(1) Thank you SO MUCH for your interest here!
(2) Thank you SO MUCH for making a clear proposal FIRST before sending complex CLs (I hate that we have a different name for them than PRs, whatever)
(3) I have no "product" objections. Glad you're thinking about Wasm here. I think your approach w/ JS is fine. Forever our typed data has mostly been modeled off what's possible in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray – I think it's good to think beyond it.
I'll leave it up to the engineers that you CC'd to give a go-ahead to proceed. It's ideal that we come to some agreement about design/scope before you start sending CLs, although if you get things working on your own machine and you have initial thoughts, performance numbers, that's great to see!
lrhn commentedon May 28, 2026
Main objection would be that they are not directly supported by JS.
But then, neither is
Int64List. (IsInt32x4?)Using it for
OneByteStringrequires a way to get the bytes of a string as aByteBuffer. That could leak the implementation of it treats one- and two-byte strings differently, so possibly not something that can be publicly available.(And if it's only internal, it doesn't need to be defined in Dart.)
I have no real problem with the idea if the implementeres can agree.
modulovalue commentedon May 28, 2026
Yes, exactly. It would be used within the SDK and not implemented in user space.
Yes, with dart2js it compiles to a scalar variant.
mraleph commentedon May 29, 2026
While I am not per-se opposed to adding
Int8x16and associated types, I am concerned that approach we have chosen with existing SIMD types does not scale:If we want to land something for core libraries then I think we should start by keeping necessary additions private (as sort of hidden types and helpers) rather than adding them as public types.
In the long run I think we should seriously consider deprecating existing SIMD types and developing new SIMD APIs which are target specific instead.
mraleph commentedon May 29, 2026
I don't know what benchmark is - but I would imagine that just calling existing highly optimized C function is possibly a better strategy then porting all of this to Dart. The same with JSON parsing.
modulovalue commentedon May 29, 2026
@mraleph All fair points, here are my thoughts:
Width concerns
There are two categories we need to differentiate when talking about SIMD: fixed length and variable length.
Fixed length is well supported by most of our users (arm & wasm)
Variable length is not, e.g., SVE is not practically available in the app-developer toolchains on Apple or recent Android targets, even where the hardware supports it.
When it comes to fixed length SIMD support, I think it's unlikely that bigger lanes are going to be relevant to us in the near future.
Focusing on what's supported everywhere (128-bit) is suboptimal for server x86 use-cases, but covers essentially all Dart users.
Future direction
I discussed this briefly with @kevmoo (who has started exploring improvements to our SIMD story in https://dart-review.googlesource.com/c/sdk/+/481980 and https://github.com/kevmoo/sdk/blob/simd_proposal/SIMD_proposal.md). If we want to have an optimal fixed-length SIMD story, I think a layered approach would be good and here's what it could look like (this is not a proposal, but a future direction we could take that is compatible with our current approach):
Int8x?,Int16x?,Int32x?.nibbleSearch,byteSearch,simdBinarySearch.Dart lacks type-level values, so we can't easily abstract over
Int8x?, so I think it's reasonable to work with what we have right now, and expand it toInt8x32/Int8x64in the future if needed.Compatibility concerns
Agreed, but in practice arm and wasm users get full benefit from a 128-bit API. Here are numbers from my POC for
OneByteString.indexOf:This is a HUGE improvement and these wins will be able to cascade into other parts of the SDK and ecosystem: LineSplitter, hex/base64/json parsing.
Yes, but that optimizes String.indexOf specifically. The proposal here is to unlock byte-level SIMD as a primitive, with
String.indexOfas one of many beneficiaries.lrhn commentedon May 30, 2026
The number of times I've heard "The PC is dead" makes me question that assumption.
If nothing else, people will be using x64-based PCs while developing.
[vm/compiler] Intrinsify Int32x4 integer lane getters (x/y/z/w).
[dart2wasm] Use WasmV128 for I32x4 backing storage
rakudrama commentedon Jul 15, 2026
I'm not clear on how
Int8x16helps withString.indexOf. There is no API to view the string bytes as typed data, and no guarantees as to the alignment of the data.modulovalue commentedon Jul 15, 2026
@rakudrama That's right, users can't and shouldn't, but we have more options from within the sdk. (Note that this is meant to just be a simple motivating example that everyone can directly benefit from, but there are plenty more that users will be able to write themselves.)
rakudrama commentedon Jul 24, 2026
Is there a simple motivating example that is actually feasible? Providing
Int8x16is not going to makeString.indexOffaster.AFAIK there is no requirement for
Uint8Listto have (or be used as) a buffer with 16-byte alignment.Generally, it is safe only to create views with same-size-or-smaller elements. It doesn't matter much for scalar values, since there is some minimum word-alignment, but it would be wasteful to demand that an in-heap
new Uint8List(32)has 16-byte or 32-byte alignment at the first byte of the data just in case it might be used as a buffer for someInt8x16Listor some future 256-bit SIMD view.The alignment considerations add weight to @mraleph's concern that our approach to SIMD is not scalable and should be reconsidered.
[dart2wasm] Unify unmodifiable typed data mixins
mkustermann commentedon Jul 29, 2026
I share similar concerns to @mraleph 's #63471 (comment) .
On a high level I would want us to have two layers:
Expose the full power of HW/backend-platform to Dart users: We have been working towards this goal on multiple fronts:
dart:_wasmlibrary that we eventually intend to extend and make public / usable by end users (issue)=> These things are somewhat outside the Dart language, where a subset of Dart is a DSL to express C code and Wasm code with compile-time errors if one uses them wrong.
=> Users could then make packages that depend on these low level, backend-specific libraries and possibly use conditional imports to abstract over them across backends.
Make the core
dart:*libraries (i.e. excluding backend specific ones likedart:ffi) be nice to use, feature rich and performant.=> These are Dart language features.
=> We try not to put things into those libraries that a) only work on some backends but not all or b) only perform well on backends and terribly on others
These two differ materially: For example
dart:typed_dataabstractions are never "optimal": For exampleInt32xListis a wrapper object around aWasmArrayobject -- 2 objects in the heap)Uint8Listcould be backed by different implementations and as a result a call site cannot always devirtualize[]/[]=calls and make them simple byte loads/stores -- e.g. in wasm)=> So if one wants to get the most performance / size - the pure power of HW - then high level
dart:typed_dataAPI may actually not be the best to get there.We have some other requests similar to this
Int8x16that somewhat fall in the middle between these two - where there is tension whether it should be part of the language / core libraries or some backend specific functionality:Float16List,BFloat16List([typed_data] AddFloat16List+ [vm/ffi] AddFloat16#56319)int16,int32, ... (proposal to introduce a rich, efficient and more flexible type system in dart 3.x #51994)My view is that we can put something in to common core libraries iff
[dart2wasm] Back Int32x4List with WasmArray<WasmV128>
[typed_data] Add Int32x4.equal and anyTrue
krll-kov commentedon Sep 4, 2026
Here is one from user space, if it helps. I'm porting libwebp's VP8 encoder to Dart. Its inner kernels are sums of squared differences, a Hadamard distortion metric, a 4x4 DCT and a quantizer, all reading 8-bit samples out of a macroblock cache. libwebp has NEON and SSE versions of all of them.
Unlike
String.indexOf, the data is already aUint8Listthat the codec allocates and lays out itself, so there is no missing view API and nothing internal to expose. The layout is the one libwebp uses: rows 32 bytes apart, 16 luma rows starting at offset 0. Every 16-byte row is therefore already 16-byte aligned and anInt8x16List.viewover it would work as is.The alignment concern does apply to half of it. The 4x4 sub-blocks sit at offsets 0, 4, 8 and 12 within a row, so three out of four are not 16-aligned. Those kernels would need a 16-byte load from an arbitrary offset, or a way to fill lanes from four separate 4-byte groups.
What the byte path costs today, with
Float32x4, which is the only thing available now.dart compile exe, macos_arm64:sse16x16hadamard4x4The Hadamard kernel loses even though everything it needs is available and in range. The transform is separable and its passes commute, so it needs a single 4x4 transpose, done with the usual eight
shuffleMix. What is left is filling four vectors from sixteen strided bytes, and on a 4x4 block that costs more than four-wide arithmetic saves. For reference, the same arithmetic over an already populatedFloat32x4Listruns 2.5x faster than the scalar loop, so that is roughly the ceiling a cheap byte to lane path would be buying.So for this workload the value of a byte lane type is not the byte arithmetic. It is that sixteen byte loads and four vector constructions collapse into one load.
Both kernels, in the shape of
benchmarks/SimdInt32x4.mainchecks the two variants agree before either is timed.