[vm/compiler] Forward typed data view constructor arguments.

Also adds tests for load optimizer fixes in 836c04f.

Bug: https://github.com/dart-lang/sdk/issues/36570
Change-Id: Ie8ea7f944bc789b4c0894f9d96010113fb3ec4b6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/99466
Commit-Queue: Teagan Strickland <sstrickl@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index df912de..dee6cab 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -2632,6 +2632,30 @@
   }
 }
 
+bool LoadFieldInstr::IsTypedDataViewFactory(const Function& function) {
+  auto kind = MethodRecognizer::RecognizeKind(function);
+  switch (kind) {
+    case MethodRecognizer::kTypedData_ByteDataView_factory:
+    case MethodRecognizer::kTypedData_Int8ArrayView_factory:
+    case MethodRecognizer::kTypedData_Uint8ArrayView_factory:
+    case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory:
+    case MethodRecognizer::kTypedData_Int16ArrayView_factory:
+    case MethodRecognizer::kTypedData_Uint16ArrayView_factory:
+    case MethodRecognizer::kTypedData_Int32ArrayView_factory:
+    case MethodRecognizer::kTypedData_Uint32ArrayView_factory:
+    case MethodRecognizer::kTypedData_Int64ArrayView_factory:
+    case MethodRecognizer::kTypedData_Uint64ArrayView_factory:
+    case MethodRecognizer::kTypedData_Float32ArrayView_factory:
+    case MethodRecognizer::kTypedData_Float64ArrayView_factory:
+    case MethodRecognizer::kTypedData_Float32x4ArrayView_factory:
+    case MethodRecognizer::kTypedData_Int32x4ArrayView_factory:
+    case MethodRecognizer::kTypedData_Float64x2ArrayView_factory:
+      return true;
+    default:
+      return false;
+  }
+}
+
 Definition* ConstantInstr::Canonicalize(FlowGraph* flow_graph) {
   return HasUses() ? this : NULL;
 }
@@ -2703,6 +2727,16 @@
       if (call->is_known_list_constructor() &&
           IsFixedLengthArrayCid(call->Type()->ToCid())) {
         return call->ArgumentAt(1);
+      } else if (IsTypedDataViewFactory(call->function())) {
+        // Typed data view factories all take three arguments (after
+        // the implicit type arguments parameter):
+        //
+        // 1) _TypedList buffer -- the underlying data for the view
+        // 2) int offsetInBytes -- the offset into the buffer to start viewing
+        // 3) int length        -- the number of elements in the view
+        //
+        // Here, we forward the third.
+        return call->ArgumentAt(3);
       }
     } else if (CreateArrayInstr* create_array = array->AsCreateArray()) {
       if (slot().kind() == Slot::Kind::kArray_length) {
@@ -2719,6 +2753,24 @@
         }
       }
     }
+  } else if (slot().kind() == Slot::Kind::kTypedDataView_data) {
+    // This case cover the first explicit argument to typed data view
+    // factories, the data (buffer).
+    Definition* array = instance()->definition()->OriginalDefinition();
+    if (StaticCallInstr* call = array->AsStaticCall()) {
+      if (IsTypedDataViewFactory(call->function())) {
+        return call->ArgumentAt(1);
+      }
+    }
+  } else if (slot().kind() == Slot::Kind::kTypedDataView_offset_in_bytes) {
+    // This case cover the second explicit argument to typed data view
+    // factories, the offset into the buffer.
+    Definition* array = instance()->definition()->OriginalDefinition();
+    if (StaticCallInstr* call = array->AsStaticCall()) {
+      if (IsTypedDataViewFactory(call->function())) {
+        return call->ArgumentAt(2);
+      }
+    }
   } else if (slot().IsTypeArguments()) {
     Definition* array = instance()->definition()->OriginalDefinition();
     if (StaticCallInstr* call = array->AsStaticCall()) {
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index b1b9d4c..5450f5f 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -5303,6 +5303,7 @@
   virtual Definition* Canonicalize(FlowGraph* flow_graph);
 
   static bool IsFixedLengthArrayCid(intptr_t cid);
+  static bool IsTypedDataViewFactory(const Function& function);
 
   virtual bool AllowsCSE() const { return slot_.is_immutable(); }
   virtual bool HasUnknownSideEffects() const { return false; }
diff --git a/runtime/vm/compiler/backend/redundancy_elimination_test.cc b/runtime/vm/compiler/backend/redundancy_elimination_test.cc
index e06aca3..e33455e 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination_test.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination_test.cc
@@ -551,4 +551,170 @@
                        /* make_host_escape= */ true, MakeAssertAssignable);
 }
 
+// This test verifies behavior of load forwarding when an alias for an
+// allocation A is created after forwarded due to an eliminated load. That is,
+// allocation A is stored and later retrieved via load B, B is used in store C
+// (with a different constant index/index_scale than in B but that overlaps),
+// and then A is retrieved again (with the same index as in B) in load D.
+//
+// When B gets eliminated and replaced with in C and D with A, the store in C
+// should stop the load D from being eliminated. This is a scenario that came
+// up when forwarding typed data view factory arguments.
+//
+// Here, the entire scenario happens within a single basic block.
+ISOLATE_UNIT_TEST_CASE(LoadOptimizer_AliasingViaLoadElimination_SingleBlock) {
+  const char* kScript = R"(
+    import 'dart:typed_data';
+
+    testViewAliasing1() {
+      final f64 = new Float64List(1);
+      final f32 = new Float32List.view(f64.buffer);
+      f64[0] = 1.0; // Should not be forwarded.
+      f32[1] = 2.0; // upper 32bits for 2.0f and 2.0 are the same
+      return f64[0];
+    }
+  )";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& function =
+      Function::Handle(GetFunction(root_library, "testViewAliasing1"));
+
+  Invoke(root_library, "testViewAliasing1");
+
+  TestPipeline pipeline(function, CompilerPass::kJIT);
+  FlowGraph* flow_graph = pipeline.RunPasses({});
+
+  auto entry = flow_graph->graph_entry()->normal_entry();
+  EXPECT(entry != nullptr);
+
+  StaticCallInstr* list_factory = nullptr;
+  UnboxedConstantInstr* double_one = nullptr;
+  StoreIndexedInstr* first_store = nullptr;
+  StoreIndexedInstr* second_store = nullptr;
+  LoadIndexedInstr* final_load = nullptr;
+  BoxInstr* boxed_result = nullptr;
+
+  ILMatcher cursor(flow_graph, entry);
+  RELEASE_ASSERT(cursor.TryMatch({
+      kMoveGlob,
+      {kMatchAndMoveStaticCall, &list_factory},
+      kMoveGlob,
+      {kMatchAndMoveUnboxedConstant, &double_one},
+      kMoveGlob,
+      {kMatchAndMoveStoreIndexed, &first_store},
+      kMoveGlob,
+      {kMatchAndMoveStoreIndexed, &second_store},
+      kMoveGlob,
+      {kMatchAndMoveLoadIndexed, &final_load},
+      kMoveGlob,
+      {kMatchAndMoveBox, &boxed_result},
+      kMoveGlob,
+      kMatchReturn,
+  }));
+
+  EXPECT(first_store->array()->definition() == list_factory);
+  EXPECT(second_store->array()->definition() == list_factory);
+  EXPECT(boxed_result->value()->definition() != double_one);
+  EXPECT(boxed_result->value()->definition() == final_load);
+}
+
+// This test verifies behavior of load forwarding when an alias for an
+// allocation A is created after forwarded due to an eliminated load. That is,
+// allocation A is stored and later retrieved via load B, B is used in store C
+// (with a different constant index/index_scale than in B but that overlaps),
+// and then A is retrieved again (with the same index as in B) in load D.
+//
+// When B gets eliminated and replaced with in C and D with A, the store in C
+// should stop the load D from being eliminated. This is a scenario that came
+// up when forwarding typed data view factory arguments.
+//
+// Here, the scenario is split across basic blocks. This is a cut-down version
+// of language_2/vm/load_to_load_forwarding_vm_test.dart with just enough extra
+// to keep testViewAliasing1 from being optimized into a single basic block.
+// Thus, this test may be brittler than the other, if future work causes it to
+// end up compiled into a single basic block (or a simpler set of basic blocks).
+ISOLATE_UNIT_TEST_CASE(LoadOptimizer_AliasingViaLoadElimination_AcrossBlocks) {
+  const char* kScript = R"(
+    import 'dart:typed_data';
+
+    class Expect {
+      static void equals(var a, var b) {}
+      static void listEquals(var a, var b) {}
+    }
+
+    testViewAliasing1() {
+      final f64 = new Float64List(1);
+      final f32 = new Float32List.view(f64.buffer);
+      f64[0] = 1.0; // Should not be forwarded.
+      f32[1] = 2.0; // upper 32bits for 2.0f and 2.0 are the same
+      return f64[0];
+    }
+
+    testViewAliasing2() {
+      final f64 = new Float64List(2);
+      final f64v = new Float64List.view(f64.buffer,
+                                        Float64List.bytesPerElement);
+      f64[1] = 1.0; // Should not be forwarded.
+      f64v[0] = 2.0;
+      return f64[1];
+    }
+
+    testViewAliasing3() {
+      final u8 = new Uint8List(Float64List.bytesPerElement * 2);
+      final f64 = new Float64List.view(u8.buffer, Float64List.bytesPerElement);
+      f64[0] = 1.0; // Should not be forwarded.
+      u8[15] = 0x40;
+      u8[14] = 0x00;
+      return f64[0];
+    }
+
+    main() {
+      for (var i = 0; i < 20; i++) {
+        Expect.equals(2.0, testViewAliasing1());
+        Expect.equals(2.0, testViewAliasing2());
+        Expect.equals(2.0, testViewAliasing3());
+      }
+    }
+  )";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& function =
+      Function::Handle(GetFunction(root_library, "testViewAliasing1"));
+
+  Invoke(root_library, "main");
+
+  TestPipeline pipeline(function, CompilerPass::kJIT);
+  FlowGraph* flow_graph = pipeline.RunPasses({});
+
+  auto entry = flow_graph->graph_entry()->normal_entry();
+  EXPECT(entry != nullptr);
+
+  StaticCallInstr* list_factory = nullptr;
+  UnboxedConstantInstr* double_one = nullptr;
+  StoreIndexedInstr* first_store = nullptr;
+  StoreIndexedInstr* second_store = nullptr;
+  LoadIndexedInstr* final_load = nullptr;
+  BoxInstr* boxed_result = nullptr;
+
+  ILMatcher cursor(flow_graph, entry);
+  RELEASE_ASSERT(cursor.TryMatch({
+      kMoveGlob, {kMatchAndMoveStaticCall, &list_factory},
+      kMoveGlob, kMatchAndMoveBranchTrue,
+      kMoveGlob, kMatchAndMoveBranchTrue,
+      kMoveGlob, kMatchAndMoveBranchFalse,
+      kMoveGlob, {kMatchAndMoveUnboxedConstant, &double_one},
+      kMoveGlob, {kMatchAndMoveStoreIndexed, &first_store},
+      kMoveGlob, kMatchAndMoveBranchFalse,
+      kMoveGlob, {kMatchAndMoveStoreIndexed, &second_store},
+      kMoveGlob, {kMatchAndMoveLoadIndexed, &final_load},
+      kMoveGlob, {kMatchAndMoveBox, &boxed_result},
+      kMoveGlob, kMatchReturn,
+  }));
+
+  EXPECT(first_store->array()->definition() == list_factory);
+  EXPECT(second_store->array()->definition() == list_factory);
+  EXPECT(boxed_result->value()->definition() != double_one);
+  EXPECT(boxed_result->value()->definition() == final_load);
+}
+
 }  // namespace dart
diff --git a/tests/language_2/vm/load_to_load_forwarding_cutdown_vm_test.dart b/tests/language_2/vm/load_to_load_forwarding_cutdown_vm_test.dart
new file mode 100644
index 0000000..ed81342
--- /dev/null
+++ b/tests/language_2/vm/load_to_load_forwarding_cutdown_vm_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2012, 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.
+
+// Test correctness of side effects tracking used by load to load forwarding.
+// In this cutdown version of the load_to_load_forwarding_vm test, the function
+// being compiled ends up in a single basic block, which tests load
+// elimination when generating the initial sets.
+
+// VMOptions=--optimization-counter-threshold=10 --no-background-compilation
+
+import "package:expect/expect.dart";
+import "dart:typed_data";
+
+testViewAliasing1() {
+  final f64 = new Float64List(1);
+  final f32 = new Float32List.view(f64.buffer);
+  f64[0] = 1.0; // Should not be forwarded.
+  f32[1] = 2.0; // upper 32bits for 2.0f and 2.0 are the same
+  return f64[0];
+}
+
+main() {
+  for (var i = 0; i < 20; i++) {
+    Expect.equals(2.0, testViewAliasing1());
+  }
+}