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