Version 2.17.0-174.0.dev
Merge commit 'c97736d594d9038d2f590ff2a71b26f14c54024e' into 'dev'
diff --git a/runtime/docs/infra/il_tests.md b/runtime/docs/infra/il_tests.md
index c8c2b40..44b948c 100644
--- a/runtime/docs/infra/il_tests.md
+++ b/runtime/docs/infra/il_tests.md
@@ -11,6 +11,17 @@
IL tests are placed in files ending with `_il_test.dart`.
+To run an IL test you need to use `tools/test.py` runner with AOT configuration:
+
+```
+# Run against ReleaseX64 AOT compiler
+$ tools/test.py -n dartkp-linux-release-x64 $path_to_an_il_test
+$ tools/test.py -c dartkp -m release $path_to_an_il_test
+```
+
+Tests require `gen_snapshot`, `dart_precompiled_runtime` and
+`vm_platform_strong.dill` to be built for the target configuration.
+
Each IL test should contain one or more of the functions marked with a
`@pragma('vm:testing:print-flow-graph'[, 'phases filter'])`.
diff --git a/runtime/tests/vm/dart/flutter_regress_98466_il_test.dart b/runtime/tests/vm/dart/flutter_regress_98466_il_test.dart
new file mode 100644
index 0000000..faf771f
--- /dev/null
+++ b/runtime/tests/vm/dart/flutter_regress_98466_il_test.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2022, 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.
+
+import 'package:expect/expect.dart';
+import 'package:vm/testing/il_matchers.dart';
+
+// This test creates a phi which has multiple inputs referring to the same
+// AllocateObject instruction. When delaying this allocation we need to
+// look at all of these inputs and not just at the first one.
+
+bool shouldPrint = false;
+
+@pragma('vm:never-inline')
+void blackhole(Object v) {
+ if (shouldPrint) {
+ print(v);
+ }
+}
+
+class X {
+ dynamic field;
+
+ @override
+ String toString() => 'X($field)';
+}
+
+// This function is used to create a phi with three arguments two of which
+// point to the same definition: original value of [v].
+@pragma('vm:prefer-inline')
+X decisionTree(bool a, bool b, X v) {
+ if (a) {
+ v.field = 10;
+ blackhole(v);
+ return v;
+ } else if (b) {
+ return v;
+ } else {
+ return X();
+ }
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
+dynamic testDelayAllocationsUnsunk(bool a, bool b) {
+ // Allocation is expected to be unsunk because no use dominates all other
+ // uses.
+ var v = X();
+ if (a) {
+ blackhole(b);
+ }
+ v = decisionTree(a, b, v);
+ blackhole(v);
+ return v.field;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
+dynamic testDelayAllocationsSunk(bool a, bool b) {
+ var v = X();
+ if (a) {
+ blackhole(b);
+ }
+ v.field = 42; // Allocation is expected to be sunk past if to this use.
+ v = decisionTree(a, b, v);
+ blackhole(v);
+ return v.field;
+}
+
+List<dynamic> testAllVariants(dynamic Function(bool, bool) f) {
+ return [
+ for (var a in [true, false])
+ for (var b in [true, false]) f(a, b),
+ ];
+}
+
+void main(List<String> args) {
+ shouldPrint = args.contains("shouldPrint");
+
+ Expect.listEquals(
+ [10, 10, null, null], testAllVariants(testDelayAllocationsUnsunk));
+ Expect.listEquals(
+ [10, 10, 42, null], testAllVariants(testDelayAllocationsSunk));
+}
+
+void matchIL$testDelayAllocationsUnsunk(FlowGraph afterDelayAllocations) {
+ afterDelayAllocations.dump();
+ afterDelayAllocations.match([
+ match.block('Graph'),
+ match.block('Function', [
+ // Allocation must stay unsunk
+ match.AllocateObject()
+ ])
+ ]);
+}
+
+void matchIL$testDelayAllocationsSunk(FlowGraph afterDelayAllocations) {
+ afterDelayAllocations.dump();
+ afterDelayAllocations.match([
+ match.block('Graph'),
+ match.block('Function', [
+ // Allocation must be sunk from this block.
+ match.Branch(match.StrictCompare(match.any, match.any, kind: '==='),
+ ifTrue: 'B3', ifFalse: 'B4'),
+ ]),
+ 'B3' <<
+ match.block('Target', [
+ match.Goto('B5'),
+ ]),
+ 'B4' <<
+ match.block('Target', [
+ match.Goto('B5'),
+ ]),
+ 'B5' <<
+ match.block('Join', [
+ match.AllocateObject(),
+ ]),
+ ]);
+}
diff --git a/runtime/tests/vm/dart_2/flutter_regress_98466_il_test.dart b/runtime/tests/vm/dart_2/flutter_regress_98466_il_test.dart
new file mode 100644
index 0000000..faf771f
--- /dev/null
+++ b/runtime/tests/vm/dart_2/flutter_regress_98466_il_test.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2022, 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.
+
+import 'package:expect/expect.dart';
+import 'package:vm/testing/il_matchers.dart';
+
+// This test creates a phi which has multiple inputs referring to the same
+// AllocateObject instruction. When delaying this allocation we need to
+// look at all of these inputs and not just at the first one.
+
+bool shouldPrint = false;
+
+@pragma('vm:never-inline')
+void blackhole(Object v) {
+ if (shouldPrint) {
+ print(v);
+ }
+}
+
+class X {
+ dynamic field;
+
+ @override
+ String toString() => 'X($field)';
+}
+
+// This function is used to create a phi with three arguments two of which
+// point to the same definition: original value of [v].
+@pragma('vm:prefer-inline')
+X decisionTree(bool a, bool b, X v) {
+ if (a) {
+ v.field = 10;
+ blackhole(v);
+ return v;
+ } else if (b) {
+ return v;
+ } else {
+ return X();
+ }
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
+dynamic testDelayAllocationsUnsunk(bool a, bool b) {
+ // Allocation is expected to be unsunk because no use dominates all other
+ // uses.
+ var v = X();
+ if (a) {
+ blackhole(b);
+ }
+ v = decisionTree(a, b, v);
+ blackhole(v);
+ return v.field;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
+dynamic testDelayAllocationsSunk(bool a, bool b) {
+ var v = X();
+ if (a) {
+ blackhole(b);
+ }
+ v.field = 42; // Allocation is expected to be sunk past if to this use.
+ v = decisionTree(a, b, v);
+ blackhole(v);
+ return v.field;
+}
+
+List<dynamic> testAllVariants(dynamic Function(bool, bool) f) {
+ return [
+ for (var a in [true, false])
+ for (var b in [true, false]) f(a, b),
+ ];
+}
+
+void main(List<String> args) {
+ shouldPrint = args.contains("shouldPrint");
+
+ Expect.listEquals(
+ [10, 10, null, null], testAllVariants(testDelayAllocationsUnsunk));
+ Expect.listEquals(
+ [10, 10, 42, null], testAllVariants(testDelayAllocationsSunk));
+}
+
+void matchIL$testDelayAllocationsUnsunk(FlowGraph afterDelayAllocations) {
+ afterDelayAllocations.dump();
+ afterDelayAllocations.match([
+ match.block('Graph'),
+ match.block('Function', [
+ // Allocation must stay unsunk
+ match.AllocateObject()
+ ])
+ ]);
+}
+
+void matchIL$testDelayAllocationsSunk(FlowGraph afterDelayAllocations) {
+ afterDelayAllocations.dump();
+ afterDelayAllocations.match([
+ match.block('Graph'),
+ match.block('Function', [
+ // Allocation must be sunk from this block.
+ match.Branch(match.StrictCompare(match.any, match.any, kind: '==='),
+ ifTrue: 'B3', ifFalse: 'B4'),
+ ]),
+ 'B3' <<
+ match.block('Target', [
+ match.Goto('B5'),
+ ]),
+ 'B4' <<
+ match.block('Target', [
+ match.Goto('B5'),
+ ]),
+ 'B5' <<
+ match.block('Join', [
+ match.AllocateObject(),
+ ]),
+ ]);
+}
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index 1a5a835..f694613b 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -5759,6 +5759,7 @@
DECLARE_OBJECT_STORE_FIELD,
DECLARE_OBJECT_STORE_FIELD,
DECLARE_OBJECT_STORE_FIELD,
+ DECLARE_OBJECT_STORE_FIELD,
DECLARE_OBJECT_STORE_FIELD)
#undef DECLARE_OBJECT_STORE_FIELD
};
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index 35070d4..337fcac 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -1671,34 +1671,13 @@
uses.Insert(use);
}
- // Find the dominant use.
- Instruction* dominant_use = nullptr;
- auto use_it = uses.GetIterator();
- while (auto use = use_it.Next()) {
- // Start with the instruction before the use, then walk backwards through
- // blocks in the dominator chain until we hit the definition or another use.
- Instruction* instr = nullptr;
- if (auto phi = (*use)->AsPhi()) {
- // For phi uses, the dominant use only has to dominate the
- // predecessor block corresponding to the phi input.
- ASSERT(phi->InputCount() == phi->block()->PredecessorCount());
- for (intptr_t i = 0; i < phi->InputCount(); i++) {
- if (phi->InputAt(i)->definition() == def) {
- instr = phi->block()->PredecessorAt(i)->last_instruction();
- break;
- }
- }
- ASSERT(instr != nullptr);
- } else {
- instr = (*use)->previous();
- }
-
- bool dominated = false;
+ // Returns |true| iff |instr| or any instruction dominating it are either a
+ // a |def| or a use of a |def|.
+ auto is_dominated_by_another_use = [&](Instruction* instr) {
while (instr != def) {
if (uses.HasKey(instr)) {
- // We hit another use.
- dominated = true;
- break;
+ // We hit another use, meaning that this use dominates the given |use|.
+ return true;
}
if (auto block = instr->AsBlockEntry()) {
instr = block->dominator()->last_instruction();
@@ -1706,7 +1685,38 @@
instr = instr->previous();
}
}
- if (!dominated) {
+ return false;
+ };
+
+ // Find the dominant use.
+ Instruction* dominant_use = nullptr;
+ auto use_it = uses.GetIterator();
+ while (auto use = use_it.Next()) {
+ bool dominated_by_another_use = false;
+
+ if (auto phi = (*use)->AsPhi()) {
+ // For phi uses check that the dominant use dominates all
+ // predecessor blocks corresponding to matching phi inputs.
+ ASSERT(phi->InputCount() == phi->block()->PredecessorCount());
+ dominated_by_another_use = true;
+ for (intptr_t i = 0; i < phi->InputCount(); i++) {
+ if (phi->InputAt(i)->definition() == def) {
+ if (!is_dominated_by_another_use(
+ phi->block()->PredecessorAt(i)->last_instruction())) {
+ dominated_by_another_use = false;
+ break;
+ }
+ }
+ }
+ } else {
+ // Start with the instruction before the use, then walk backwards through
+ // blocks in the dominator chain until we hit the definition or
+ // another use.
+ dominated_by_another_use =
+ is_dominated_by_another_use((*use)->previous());
+ }
+
+ if (!dominated_by_another_use) {
if (dominant_use != nullptr) {
// More than one use reached the definition, which means no use
// dominates all other uses.
diff --git a/runtime/vm/compiler/backend/slot.h b/runtime/vm/compiler/backend/slot.h
index 65cafe9..514c7ee 100644
--- a/runtime/vm/compiler/backend/slot.h
+++ b/runtime/vm/compiler/backend/slot.h
@@ -117,6 +117,17 @@
V(UnhandledException, UntaggedUnhandledException, exception, Dynamic, FINAL) \
V(UnhandledException, UntaggedUnhandledException, stacktrace, Dynamic, FINAL)
+// Don't use Object or Instance, use Dynamic instead. The cid here should
+// correspond to an exact type or Dynamic, not a static type.
+// If we ever get a field of which the exact type is Instance (not a subtype),
+// update the check below.
+#define FOR_EACH_NATIVE_SLOT(_, __, ___, field_type, ____) \
+ static_assert(k##field_type##Cid != kObjectCid); \
+ static_assert(k##field_type##Cid != kInstanceCid);
+NULLABLE_BOXED_NATIVE_SLOTS_LIST(FOR_EACH_NATIVE_SLOT)
+NONNULLABLE_BOXED_NATIVE_SLOTS_LIST(FOR_EACH_NATIVE_SLOT)
+#undef FOR_EACH_NATIVE_SLOT
+
// Only define AOT-only unboxed native slots when in the precompiler. See
// UNBOXED_NATIVE_SLOTS_LIST for the format.
#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc
index 81c5977..23f9b4d 100644
--- a/runtime/vm/object_store.cc
+++ b/runtime/vm/object_store.cc
@@ -105,6 +105,7 @@
EMIT_FIELD_INIT,
EMIT_FIELD_INIT,
EMIT_FIELD_INIT,
+ EMIT_FIELD_INIT,
EMIT_FIELD_INIT)
#undef EMIT_FIELD_INIT
// Just to prevent a trailing comma.
@@ -140,7 +141,8 @@
#define EMIT_FIELD_NAME(type, name) #name "_",
OBJECT_STORE_FIELD_LIST(
EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME,
- EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME)
+ EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME,
+ EMIT_FIELD_NAME)
#undef EMIT_FIELD_NAME
};
ObjectPtr* current = from();
@@ -461,6 +463,13 @@
}
}
+void ObjectStore::LazyInitFfiMembers() {
+ auto* const thread = Thread::Current();
+ SafepointWriteRwLocker locker(thread,
+ thread->isolate_group()->program_lock());
+ // TODO(http://dartbug.com/47777): Implement finalizers.
+}
+
void ObjectStore::LazyInitIsolateMembers() {
auto* const thread = Thread::Current();
SafepointWriteRwLocker locker(thread,
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 88d7a2e..f86b8c5 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -43,7 +43,8 @@
// LAZY_ISOLATE - needs lazy init getter for a "dart:isolate" member
// LAZY_INTERNAL - needs lazy init getter for a "dart:_internal" member
#define OBJECT_STORE_FIELD_LIST(R_, RW, ARW_RELAXED, ARW_AR, LAZY_CORE, \
- LAZY_ASYNC, LAZY_ISOLATE, LAZY_INTERNAL) \
+ LAZY_ASYNC, LAZY_ISOLATE, LAZY_INTERNAL, \
+ LAZY_FFI) \
LAZY_CORE(Class, list_class) \
LAZY_CORE(Type, non_nullable_list_rare_type) \
LAZY_CORE(Type, non_nullable_map_rare_type) \
@@ -239,7 +240,6 @@
RW(GrowableObjectArray, ffi_callback_functions) \
RW(Class, ffi_pointer_class) \
RW(Class, ffi_native_type_class) \
- RW(Object, ffi_as_function_internal) \
// Please remember the last entry must be referred in the 'to' function below.
#define OBJECT_STORE_STUB_CODE_LIST(DO) \
@@ -426,6 +426,8 @@
DECLARE_LAZY_INIT_GETTER(Type, name, LazyInitIsolateMembers)
#define DECLARE_LAZY_INIT_INTERNAL_GETTER(Type, name) \
DECLARE_LAZY_INIT_GETTER(Type, name, LazyInitInternalMembers)
+#define DECLARE_LAZY_INIT_FFI_GETTER(Type, name) \
+ DECLARE_LAZY_INIT_GETTER(Type, name, LazyInitFfiMembers)
OBJECT_STORE_FIELD_LIST(DECLARE_GETTER,
DECLARE_GETTER_AND_SETTER,
DECLARE_RELAXED_ATOMIC_GETTER_AND_SETTER,
@@ -433,7 +435,8 @@
DECLARE_LAZY_INIT_CORE_GETTER,
DECLARE_LAZY_INIT_ASYNC_GETTER,
DECLARE_LAZY_INIT_ISOLATE_GETTER,
- DECLARE_LAZY_INIT_INTERNAL_GETTER)
+ DECLARE_LAZY_INIT_INTERNAL_GETTER,
+ DECLARE_LAZY_INIT_FFI_GETTER)
#undef DECLARE_OFFSET
#undef DECLARE_GETTER
#undef DECLARE_GETTER_AND_SETTER
@@ -493,6 +496,7 @@
private:
void LazyInitCoreMembers();
void LazyInitAsyncMembers();
+ void LazyInitFfiMembers();
void LazyInitIsolateMembers();
void LazyInitInternalMembers();
@@ -512,12 +516,13 @@
DECLARE_LAZY_OBJECT_STORE_FIELD,
DECLARE_LAZY_OBJECT_STORE_FIELD,
DECLARE_LAZY_OBJECT_STORE_FIELD,
+ DECLARE_LAZY_OBJECT_STORE_FIELD,
DECLARE_LAZY_OBJECT_STORE_FIELD)
#undef DECLARE_OBJECT_STORE_FIELD
#undef DECLARE_ATOMIC_OBJECT_STORE_FIELD
#undef DECLARE_LAZY_OBJECT_STORE_FIELD
ObjectPtr* to() {
- return reinterpret_cast<ObjectPtr*>(&ffi_as_function_internal_);
+ return reinterpret_cast<ObjectPtr*>(&ffi_native_type_class_);
}
ObjectPtr* to_snapshot(Snapshot::Kind kind) {
switch (kind) {
diff --git a/tools/VERSION b/tools/VERSION
index 7419b53..f2bbd92 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 173
+PRERELEASE 174
PRERELEASE_PATCH 0
\ No newline at end of file