[vm/compiler] Specialize Int32x4 operators in AOT.
The five binary operators on Int32x4 (+, -, |, &, ^) were never added
to the recognized-method list as graph intrinsics, so calls to them
were left as runtime calls through external-name bodies. In JIT the
call specializer picked kInt32x4Cid from IC feedback and emitted a
native SimdOpInstr, but AOT has no IC feedback and therefore fell back
to boxed calls, making Int32x4List inner loops 10-70x slower than both
the JIT version and a hand-written scalar equivalent.
This CL wires the same specialization paths that already exist for
Float32x4.+,-,*,/:
- recognize the five operators as graph intrinsics and mark them
with `@pragma("vm:recognized", "graph-intrinsic")` plus an
exact-result-type pragma;
- add Build_Int32x4{Add,Sub,BitAnd,BitOr,BitXor} helpers that
delegate to the existing BuildSimdOp;
- extend SimdOpInstr::KindForOperator and CreateFromCall;
- extend CallSpecializer::InlineSimdOp and TryInlineRecognizedMethod
so the non-speculative null-check path used for Float32x4 operators
in AOT also applies here.
Measured on macOS arm64 (M-series), `dart compile exe`:
Issue 63217 orSimd : 12.58 -> 0.32 us/iter (39x)
Issue 63217 andNotSimd : 23.51 -> 0.34 us/iter (69x)
Issue 53662 mandelbrot : 4038.5 -> 55.5 ms (72x)
A new benchmark benchmarks/SimdInt32x4 exercises all five operators
with a scalar and a SIMD variant so the specialization stays covered
by the benchmark bots; it is registered in Omnibus and OmnibusDeferred.
Existing tests/lib/typed_data/simd_*_test.dart still pass in JIT and
AOT.
TEST=tests/lib/typed_data/int32x4_arithmetic_test; benchmarks/SimdInt32x4
Bug: https://github.com/dart-lang/sdk/issues/53662
Bug: https://github.com/dart-lang/sdk/issues/63217
Change-Id: I9b76ab4fff228ff1a5e3d3c86f4bfc059e66a49a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/497000
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
Auto-Submit: Modestas Valauskas <valauskasmodestas@gmail.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
41aa8d42bc
commit
b74add4813
@@ -25,6 +25,7 @@ import '../../SHA1/dart/sha1.dart' as lib_SHA1;
|
||||
import '../../SHA256/dart/sha256.dart' as lib_SHA256;
|
||||
import '../../SkeletalAnimation/dart/SkeletalAnimation.dart'
|
||||
as lib_SkeletalAnimation;
|
||||
import '../../SimdInt32x4/dart/SimdInt32x4.dart' as lib_SimdInt32x4;
|
||||
import '../../SkeletalAnimationSIMD/dart/SkeletalAnimationSIMD.dart'
|
||||
as lib_SkeletalAnimationSIMD;
|
||||
import '../../SwitchFSM/dart/SwitchFSM.dart' as lib_SwitchFSM;
|
||||
@@ -45,6 +46,7 @@ final Map<String, Function()> benchmarks = {
|
||||
'RuntimeType': lib_RuntimeType.main,
|
||||
'SHA1': lib_SHA1.main,
|
||||
'SHA256': lib_SHA256.main,
|
||||
'SimdInt32x4': lib_SimdInt32x4.main,
|
||||
'SkeletalAnimation': lib_SkeletalAnimation.main,
|
||||
'SkeletalAnimationSIMD': lib_SkeletalAnimationSIMD.main,
|
||||
'SwitchFSM': lib_SwitchFSM.main,
|
||||
|
||||
@@ -24,6 +24,7 @@ import '../../RecordCollections/dart/RecordCollections.dart'
|
||||
import '../../RuntimeType/dart/RuntimeType.dart' deferred as lib_RuntimeType;
|
||||
import '../../SHA1/dart/sha1.dart' deferred as lib_SHA1;
|
||||
import '../../SHA256/dart/sha256.dart' deferred as lib_SHA256;
|
||||
import '../../SimdInt32x4/dart/SimdInt32x4.dart' deferred as lib_SimdInt32x4;
|
||||
import '../../SkeletalAnimation/dart/SkeletalAnimation.dart'
|
||||
deferred as lib_SkeletalAnimation;
|
||||
import '../../SkeletalAnimationSIMD/dart/SkeletalAnimationSIMD.dart'
|
||||
@@ -61,6 +62,7 @@ final Map<String, Lib> benchmarks = {
|
||||
'RuntimeType': Lib(lib_RuntimeType.loadLibrary, () => lib_RuntimeType.main()),
|
||||
'SHA1': Lib(lib_SHA1.loadLibrary, () => lib_SHA1.main()),
|
||||
'SHA256': Lib(lib_SHA256.loadLibrary, () => lib_SHA256.main()),
|
||||
'SimdInt32x4': Lib(lib_SimdInt32x4.loadLibrary, () => lib_SimdInt32x4.main()),
|
||||
'SkeletalAnimation': Lib(
|
||||
lib_SkeletalAnimation.loadLibrary,
|
||||
() => lib_SkeletalAnimation.main(),
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Regression benchmark for https://github.com/dart-lang/sdk/issues/63217 and
|
||||
// https://github.com/dart-lang/sdk/issues/53662: the five binary operators on
|
||||
// Int32x4 (+, -, |, &, ^) were not specialized in AOT mode, so `Int32x4List`
|
||||
// loops fell back to boxed runtime calls that ran 10-70x slower than the
|
||||
// hand-written scalar version or than the JIT.
|
||||
//
|
||||
// For every operator there is a scalar variant and a SIMD variant over the
|
||||
// same Uint32List buffer, so the benchmark suite exposes both the absolute
|
||||
// cost of each SIMD op and its speedup over the scalar baseline.
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
||||
|
||||
const int words = 2048;
|
||||
|
||||
abstract class SimdBench extends BenchmarkBase {
|
||||
SimdBench(String name) : super('SimdInt32x4.$name');
|
||||
|
||||
late final Uint32List a;
|
||||
late final Uint32List b;
|
||||
|
||||
@override
|
||||
void setup() {
|
||||
a = Uint32List(words);
|
||||
b = Uint32List(words);
|
||||
for (int i = 0; i < words; i++) {
|
||||
a[i] = 0xA5A5A5A5 ^ i;
|
||||
b[i] = 0x5A5A5A5A ^ (i * 31);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrScalar extends SimdBench {
|
||||
OrScalar() : super('orScalar');
|
||||
@override
|
||||
void run() {
|
||||
final n = a.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i] |= b[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrSimd extends SimdBench {
|
||||
OrSimd() : super('orSimd');
|
||||
@override
|
||||
void run() {
|
||||
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
||||
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
||||
for (int j = 0; j < la.length; j++) {
|
||||
la[j] = la[j] | lb[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AndScalar extends SimdBench {
|
||||
AndScalar() : super('andScalar');
|
||||
@override
|
||||
void run() {
|
||||
final n = a.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i] &= b[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AndSimd extends SimdBench {
|
||||
AndSimd() : super('andSimd');
|
||||
@override
|
||||
void run() {
|
||||
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
||||
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
||||
for (int j = 0; j < la.length; j++) {
|
||||
la[j] = la[j] & lb[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class XorScalar extends SimdBench {
|
||||
XorScalar() : super('xorScalar');
|
||||
@override
|
||||
void run() {
|
||||
final n = a.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i] ^= b[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class XorSimd extends SimdBench {
|
||||
XorSimd() : super('xorSimd');
|
||||
@override
|
||||
void run() {
|
||||
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
||||
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
||||
for (int j = 0; j < la.length; j++) {
|
||||
la[j] = la[j] ^ lb[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AddScalar extends SimdBench {
|
||||
AddScalar() : super('addScalar');
|
||||
@override
|
||||
void run() {
|
||||
final n = a.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i] = a[i] + b[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AddSimd extends SimdBench {
|
||||
AddSimd() : super('addSimd');
|
||||
@override
|
||||
void run() {
|
||||
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
||||
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
||||
for (int j = 0; j < la.length; j++) {
|
||||
la[j] = la[j] + lb[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubScalar extends SimdBench {
|
||||
SubScalar() : super('subScalar');
|
||||
@override
|
||||
void run() {
|
||||
final n = a.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i] = a[i] - b[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubSimd extends SimdBench {
|
||||
SubSimd() : super('subSimd');
|
||||
@override
|
||||
void run() {
|
||||
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
||||
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
||||
for (int j = 0; j < la.length; j++) {
|
||||
la[j] = la[j] - lb[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
final benchmarks = <BenchmarkBase Function()>[
|
||||
OrScalar.new,
|
||||
OrSimd.new,
|
||||
AndScalar.new,
|
||||
AndSimd.new,
|
||||
XorScalar.new,
|
||||
XorSimd.new,
|
||||
AddScalar.new,
|
||||
AddSimd.new,
|
||||
SubScalar.new,
|
||||
SubSimd.new,
|
||||
];
|
||||
|
||||
for (final bm in benchmarks) {
|
||||
bm()
|
||||
..setup()
|
||||
..run()
|
||||
..run();
|
||||
}
|
||||
|
||||
for (final bm in benchmarks) {
|
||||
bm().report();
|
||||
}
|
||||
}
|
||||
@@ -8604,6 +8604,16 @@ SimdOpInstr::Kind SimdOpInstr::KindForOperator(MethodRecognizer::Kind kind) {
|
||||
return SimdOpInstr::kFloat64x2Add;
|
||||
case MethodRecognizer::kFloat64x2Sub:
|
||||
return SimdOpInstr::kFloat64x2Sub;
|
||||
case MethodRecognizer::kInt32x4Add:
|
||||
return SimdOpInstr::kInt32x4Add;
|
||||
case MethodRecognizer::kInt32x4Sub:
|
||||
return SimdOpInstr::kInt32x4Sub;
|
||||
case MethodRecognizer::kInt32x4BitAnd:
|
||||
return SimdOpInstr::kInt32x4BitAnd;
|
||||
case MethodRecognizer::kInt32x4BitOr:
|
||||
return SimdOpInstr::kInt32x4BitOr;
|
||||
case MethodRecognizer::kInt32x4BitXor:
|
||||
return SimdOpInstr::kInt32x4BitXor;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -8626,6 +8636,11 @@ SimdOpInstr* SimdOpInstr::CreateFromCall(Zone* zone,
|
||||
case MethodRecognizer::kFloat64x2Div:
|
||||
case MethodRecognizer::kFloat64x2Add:
|
||||
case MethodRecognizer::kFloat64x2Sub:
|
||||
case MethodRecognizer::kInt32x4Add:
|
||||
case MethodRecognizer::kInt32x4Sub:
|
||||
case MethodRecognizer::kInt32x4BitAnd:
|
||||
case MethodRecognizer::kInt32x4BitOr:
|
||||
case MethodRecognizer::kInt32x4BitXor:
|
||||
op = new (zone) SimdOpInstr(KindForOperator(kind), call->deopt_id());
|
||||
break;
|
||||
#if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64)
|
||||
|
||||
@@ -3027,6 +3027,11 @@ static bool InlineSimdOp(FlowGraph* flow_graph,
|
||||
case MethodRecognizer::kFloat64x2Div:
|
||||
case MethodRecognizer::kFloat64x2Add:
|
||||
case MethodRecognizer::kFloat64x2Sub:
|
||||
case MethodRecognizer::kInt32x4Add:
|
||||
case MethodRecognizer::kInt32x4Sub:
|
||||
case MethodRecognizer::kInt32x4BitAnd:
|
||||
case MethodRecognizer::kInt32x4BitOr:
|
||||
case MethodRecognizer::kInt32x4BitXor:
|
||||
*last = SimdOpInstr::CreateFromCall(Z, kind, receiver, call);
|
||||
if (CompilerState::Current().is_aot()) {
|
||||
// Add null-checks in case of the arguments are known to be compatible
|
||||
@@ -3415,6 +3420,11 @@ bool CallSpecializer::TryInlineRecognizedMethod(
|
||||
case MethodRecognizer::kFloat64x2Div:
|
||||
case MethodRecognizer::kFloat64x2Add:
|
||||
case MethodRecognizer::kFloat64x2Sub:
|
||||
case MethodRecognizer::kInt32x4Add:
|
||||
case MethodRecognizer::kInt32x4Sub:
|
||||
case MethodRecognizer::kInt32x4BitAnd:
|
||||
case MethodRecognizer::kInt32x4BitOr:
|
||||
case MethodRecognizer::kInt32x4BitXor:
|
||||
return InlineSimdOp(flow_graph, is_dynamic_call, call, receiver, kind,
|
||||
graph_entry, entry, last, result);
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ static bool BuildSimdOp(FlowGraph* flow_graph, intptr_t cid, Token::Kind kind) {
|
||||
VerifyParameterIsBoxed(&builder, 1);
|
||||
|
||||
Cids* value_check = Cids::CreateMonomorphic(zone, cid);
|
||||
// Check argument. Receiver (left) is known to be a Float32x4.
|
||||
// Check argument. Receiver (left) is known to match cid by dispatch.
|
||||
builder.AddInstruction(new CheckClassInstr(new Value(right), DeoptId::kNone,
|
||||
*value_check, builder.Source()));
|
||||
Definition* left_simd = builder.AddUnboxInstr(
|
||||
@@ -387,6 +387,26 @@ bool GraphIntrinsifier::Build_Float32x4Add(FlowGraph* flow_graph) {
|
||||
return BuildSimdOp(flow_graph, kFloat32x4Cid, Token::kADD);
|
||||
}
|
||||
|
||||
bool GraphIntrinsifier::Build_Int32x4Add(FlowGraph* flow_graph) {
|
||||
return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kADD);
|
||||
}
|
||||
|
||||
bool GraphIntrinsifier::Build_Int32x4Sub(FlowGraph* flow_graph) {
|
||||
return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kSUB);
|
||||
}
|
||||
|
||||
bool GraphIntrinsifier::Build_Int32x4BitAnd(FlowGraph* flow_graph) {
|
||||
return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kBIT_AND);
|
||||
}
|
||||
|
||||
bool GraphIntrinsifier::Build_Int32x4BitOr(FlowGraph* flow_graph) {
|
||||
return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kBIT_OR);
|
||||
}
|
||||
|
||||
bool GraphIntrinsifier::Build_Int32x4BitXor(FlowGraph* flow_graph) {
|
||||
return BuildSimdOp(flow_graph, kInt32x4Cid, Token::kBIT_XOR);
|
||||
}
|
||||
|
||||
bool GraphIntrinsifier::Build_Float64x2Mul(FlowGraph* flow_graph) {
|
||||
return BuildSimdOp(flow_graph, kFloat64x2Cid, Token::kMUL);
|
||||
}
|
||||
|
||||
@@ -664,6 +664,11 @@ namespace dart {
|
||||
V(TypedDataLibrary, _Float32x4, /, Float32x4Div, 0xc08217a2) \
|
||||
V(TypedDataLibrary, _Float32x4, -, Float32x4Sub, 0xdd15548a) \
|
||||
V(TypedDataLibrary, _Float32x4, +, Float32x4Add, 0xb7dc8a19) \
|
||||
V(TypedDataLibrary, _Int32x4, +, Int32x4Add, 0xe9107b19) \
|
||||
V(TypedDataLibrary, _Int32x4, -, Int32x4Sub, 0x0e49458a) \
|
||||
V(TypedDataLibrary, _Int32x4, |, Int32x4BitOr, 0xf30a0ef5) \
|
||||
V(TypedDataLibrary, _Int32x4, &, Int32x4BitAnd, 0x0da1c43d) \
|
||||
V(TypedDataLibrary, _Int32x4, ^, Int32x4BitXor, 0x0c49417c) \
|
||||
V(TypedDataLibrary, _Float64x2, *, Float64x2Mul, 0x37439ec6) \
|
||||
V(TypedDataLibrary, _Float64x2, /, Float64x2Div, 0x12925562) \
|
||||
V(TypedDataLibrary, _Float64x2, -, Float64x2Sub, 0x2f258e89) \
|
||||
|
||||
@@ -3996,14 +3996,24 @@ class Int32x4 {
|
||||
@pragma('vm:deeply-immutable')
|
||||
@pragma("vm:entry-point")
|
||||
final class _Int32x4 implements Int32x4 {
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
@pragma("vm:exact-result-type", _Int32x4)
|
||||
@pragma("vm:external-name", "Int32x4_or")
|
||||
external Int32x4 operator |(Int32x4 other);
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
@pragma("vm:exact-result-type", _Int32x4)
|
||||
@pragma("vm:external-name", "Int32x4_and")
|
||||
external Int32x4 operator &(Int32x4 other);
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
@pragma("vm:exact-result-type", _Int32x4)
|
||||
@pragma("vm:external-name", "Int32x4_xor")
|
||||
external Int32x4 operator ^(Int32x4 other);
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
@pragma("vm:exact-result-type", _Int32x4)
|
||||
@pragma("vm:external-name", "Int32x4_add")
|
||||
external Int32x4 operator +(Int32x4 other);
|
||||
@pragma("vm:recognized", "graph-intrinsic")
|
||||
@pragma("vm:exact-result-type", _Int32x4)
|
||||
@pragma("vm:external-name", "Int32x4_sub")
|
||||
external Int32x4 operator -(Int32x4 other);
|
||||
@pragma("vm:external-name", "Int32x4_getX")
|
||||
|
||||
Reference in New Issue
Block a user