[VM] Inline ClassID.getID() eagerly, extend pattern matching logic to recognize it, use it to special case ascii decoding
This change extends/fixes the exiting "pattern recognition" which tries
to recognize the pattern
v2 <- LoadClassIdInstr(v1)
BranchIf v2 == IntegerConstant(cid)
Furthermore we start inlining the recognized `ClassID.getID` method very
early in the pipeline. This allows the VM to recognize the above
pattern and insert redefinitions before the actual inlining pass.
Furthermore we special-case two very hot methods in utf8 decoding by
manually having two loops, one of which is guarded by a class-id check
against the _Uint8ArrayView class, which is most common. (In the future
we would like to unify the typed data layouts so we no longer need to
use `ClassId.getID`, thereby also allowing non core library code to use
this).
This improves dart-aot by
* 31%+ for a protobuf decoding benchmark we care about
Issue https://github.com/dart-lang/sdk/issues/31954
Change-Id: Ia567b92b7e76ff28eda1726deaafda32732ed8f5
Reviewed-on: https://dart-review.googlesource.com/c/85281
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Jenny Messerly <jmesserly@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dev_compiler/tool/input_sdk/patch/convert_patch.dart b/pkg/dev_compiler/tool/input_sdk/patch/convert_patch.dart
index cfaa3fc..787c677 100644
--- a/pkg/dev_compiler/tool/input_sdk/patch/convert_patch.dart
+++ b/pkg/dev_compiler/tool/input_sdk/patch/convert_patch.dart
@@ -495,3 +495,13 @@
return null;
}();
}
+
+@patch
+int _scanOneByteCharacters(List<int> units, int from, int endIndex) {
+ final to = endIndex;
+ for (var i = from; i < to; i++) {
+ final unit = units[i];
+ if ((unit & _ONE_BYTE_LIMIT) != unit) return i - from;
+ }
+ return to - from;
+}
diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn
index d3bc4df..a5c2317 100644
--- a/runtime/bin/BUILD.gn
+++ b/runtime/bin/BUILD.gn
@@ -342,9 +342,7 @@
"..:dart_config",
"..:dart_os_config",
] + extra_configs
- public_configs = [
- "..:dart_public_config",
- ]
+ public_configs = [ "..:dart_public_config" ]
if (is_fuchsia) {
configs -= [ "//build/config:symbol_visibility_hidden" ]
}
diff --git a/runtime/bin/process_fuchsia.cc b/runtime/bin/process_fuchsia.cc
index f24027e2..1608bc9 100644
--- a/runtime/bin/process_fuchsia.cc
+++ b/runtime/bin/process_fuchsia.cc
@@ -615,9 +615,9 @@
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
uint32_t flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_CLONE_LDSVC |
FDIO_SPAWN_CLONE_NAMESPACE;
- status = fdio_spawn_vmo(ZX_HANDLE_INVALID, flags, vmo, program_arguments_,
- program_environment_, 4, actions, &process,
- err_msg);
+ status =
+ fdio_spawn_vmo(ZX_HANDLE_INVALID, flags, vmo, program_arguments_,
+ program_environment_, 4, actions, &process, err_msg);
if (status != ZX_OK) {
LOG_ERR("ProcessStarter: Start() fdio_spawn_vmo failed\n");
diff --git a/runtime/lib/class_id_fasta.dart b/runtime/lib/class_id_fasta.dart
index 0a89db4..c2f4ae0 100644
--- a/runtime/lib/class_id_fasta.dart
+++ b/runtime/lib/class_id_fasta.dart
@@ -14,4 +14,5 @@
static final int cidImmutableArray = 0;
static final int cidOneByteString = 0;
static final int cidTwoByteString = 0;
+ static final int cidUint8ArrayView = 0;
}
diff --git a/runtime/lib/convert_patch.dart b/runtime/lib/convert_patch.dart
index 0802cbb..3d99c0d 100644
--- a/runtime/lib/convert_patch.dart
+++ b/runtime/lib/convert_patch.dart
@@ -7,7 +7,7 @@
/// used by patches of that library. We plan to change this when we have a
/// shared front end and simply use parts.
-import "dart:_internal" show POWERS_OF_TEN, patch;
+import "dart:_internal" show POWERS_OF_TEN, patch, ClassID;
import "dart:typed_data" show Uint8List, Uint16List;
@@ -1816,3 +1816,27 @@
_sink.close();
}
}
+
+@patch
+int _scanOneByteCharacters(List<int> units, int from, int endIndex) {
+ final to = endIndex;
+
+ // Special case for _Uint8ArrayView.
+ final cid = ClassID.getID(units);
+ if (identical(cid, ClassID.cidUint8ArrayView)) {
+ if (from >= 0 && to >= 0 && to <= units.length) {
+ for (int i = from; i < to; i++) {
+ final unit = units[i];
+ if ((unit & _ONE_BYTE_LIMIT) != unit) return i - from;
+ }
+ return to - from;
+ }
+ }
+
+ // Fall through to normal case.
+ for (var i = from; i < to; i++) {
+ final unit = units[i];
+ if ((unit & _ONE_BYTE_LIMIT) != unit) return i - from;
+ }
+ return to - from;
+}
diff --git a/runtime/lib/string_patch.dart b/runtime/lib/string_patch.dart
index 103959b..e90667a 100644
--- a/runtime/lib/string_patch.dart
+++ b/runtime/lib/string_patch.dart
@@ -222,6 +222,19 @@
static String _createOneByteString(List<int> charCodes, int start, int len) {
// It's always faster to do this in Dart than to call into the runtime.
var s = _OneByteString._allocate(len);
+
+ // Special case for _Uint8ArrayView.
+ final cid = ClassID.getID(charCodes);
+ if (identical(cid, ClassID.cidUint8ArrayView)) {
+ if (start >= 0 && len >= 0) {
+ for (int i = 0; i < len; i++) {
+ s._setAt(i, charCodes[start + i]);
+ }
+ return s;
+ }
+ }
+
+ // Fall through to normal case.
for (int i = 0; i < len; i++) {
s._setAt(i, charCodes[start + i]);
}
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index bb5778a..61a6a51 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -2692,6 +2692,22 @@
return true;
}
+static bool InlineLoadClassId(FlowGraph* flow_graph,
+ Instruction* call,
+ GraphEntryInstr* graph_entry,
+ FunctionEntryInstr** entry,
+ Instruction** last) {
+ *entry =
+ new (Z) FunctionEntryInstr(graph_entry, flow_graph->allocate_block_id(),
+ call->GetBlock()->try_index(), DeoptId::kNone);
+ (*entry)->InheritDeoptTarget(Z, call);
+ auto load_cid = new (Z)
+ LoadClassIdInstr(call->PushArgumentAt(0)->value()->CopyWithType(Z));
+ flow_graph->InsertBefore(call, load_cid, nullptr, FlowGraph::kValue);
+ *last = load_cid;
+ return true;
+}
+
// Adds an explicit bounds check for a typed getter/setter.
static void PrepareInlineTypedArrayBoundsCheck(FlowGraph* flow_graph,
Instruction* call,
@@ -3605,6 +3621,8 @@
}
return InlineGetIndexed(flow_graph, kind, call, receiver, graph_entry,
entry, last, can_speculate);
+ case MethodRecognizer::kClassIDgetID:
+ return InlineLoadClassId(flow_graph, call, graph_entry, entry, last);
default:
break;
}
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 80b2c50..c81689d 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -3707,6 +3707,18 @@
CLASS_LIST_WITH_NULL(ADD_SET_FIELD)
#undef ADD_SET_FIELD
+
+#define ADD_SET_FIELD(clazz) \
+ field_name = Symbols::New(thread, "cid" #clazz "View"); \
+ field = Field::New(field_name, true, false, true, false, *this, \
+ Type::Handle(Type::IntType()), TokenPosition::kMinSource, \
+ TokenPosition::kMinSource); \
+ value = Smi::New(kTypedData##clazz##ViewCid); \
+ field.SetStaticValue(value, true); \
+ AddField(field);
+
+ CLASS_LIST_TYPED_DATA(ADD_SET_FIELD)
+#undef ADD_SET_FIELD
#undef CLASS_LIST_WITH_NULL
}
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index 23e3fde..38bfdf5 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -412,6 +412,7 @@
V(_UserTag, "_UserTag") \
V(Default, "Default") \
V(ClassID, "ClassID") \
+ V(getID, "getID") \
V(DartIsVM, "dart.isVM") \
V(stack, ":stack") \
V(stack_pointer, ":stack_pointer") \
diff --git a/sdk/lib/_internal/js_runtime/lib/convert_patch.dart b/sdk/lib/_internal/js_runtime/lib/convert_patch.dart
index 2d234a4..afb5062 100644
--- a/sdk/lib/_internal/js_runtime/lib/convert_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/convert_patch.dart
@@ -496,3 +496,13 @@
return null;
}
}
+
+@patch
+int _scanOneByteCharacters(List<int> units, int from, int endIndex) {
+ final to = endIndex;
+ for (var i = from; i < to; i++) {
+ final unit = units[i];
+ if ((unit & _ONE_BYTE_LIMIT) != unit) return i - from;
+ }
+ return to - from;
+}
diff --git a/sdk/lib/convert/utf.dart b/sdk/lib/convert/utf.dart
index 567c8b2..eb92e2b 100644
--- a/sdk/lib/convert/utf.dart
+++ b/sdk/lib/convert/utf.dart
@@ -412,16 +412,6 @@
_expectedUnits = 0;
_extraUnits = 0;
- int scanOneByteCharacters(List<int> units, int from) {
- final to = endIndex;
- final mask = _ONE_BYTE_LIMIT;
- for (var i = from; i < to; i++) {
- final unit = units[i];
- if ((unit & mask) != unit) return i - from;
- }
- return to - from;
- }
-
void addSingleBytes(int from, int to) {
assert(from >= startIndex && from <= endIndex);
assert(to >= startIndex && to <= endIndex);
@@ -484,7 +474,7 @@
}
while (i < endIndex) {
- var oneBytes = scanOneByteCharacters(codeUnits, i);
+ var oneBytes = _scanOneByteCharacters(codeUnits, i, endIndex);
if (oneBytes > 0) {
_isFirstCharacter = false;
addSingleBytes(i, i + oneBytes);
@@ -545,3 +535,10 @@
}
}
}
+
+// Returns the number of bytes in [units] starting at offset [from] which have
+// the leftmost bit set to 0.
+//
+// To increase performance of this critical method we have a special variant of
+// it implemented in the VM's patch files, which is why we make it external.
+external int _scanOneByteCharacters(List<int> units, int from, int endIndex);