[io] Faster readIntoSync for UInt8List.

Adds a specialized code path to File_ReadInto which avoids a memory copy when writing to a UInt8List.

Benchmarks:
https://docs.google.com/spreadsheets/d/1AqT5bDCaRfxalK9WLqGlm_EfVpTmeQT7LEqaJ-Hyl1c/edit?usp=sharing&resourcekey=0-NivVhxs0J1GM4BHXHK5gAQ

Tested: unit tests
Change-Id: I7d6cb5257da1724e8c61412b0f84bee22da298ab
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280560
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
diff --git a/runtime/bin/file.cc b/runtime/bin/file.cc
index 32fb230..b62d825 100644
--- a/runtime/bin/file.cc
+++ b/runtime/bin/file.cc
@@ -246,10 +246,37 @@
   Dart_Handle result = Dart_ListLength(buffer_obj, &array_len);
   ThrowIfError(result);
   ASSERT(end <= array_len);
-  uint8_t* buffer = Dart_ScopeAllocate(length);
+
+  uint8_t* buffer;
+  bool is_byte_data = false;
+
+  if (Dart_IsTypedData(buffer_obj)) {
+    // Avoid a memory copy if the input List<int> is an UInt8List.
+    Dart_TypedData_Type data_type;
+    intptr_t bytes_count;
+    ThrowIfError(Dart_TypedDataAcquireData(buffer_obj, &data_type,
+                                           reinterpret_cast<void**>(&buffer),
+                                           &bytes_count));
+    if (data_type == Dart_TypedData_kUint8) {
+      is_byte_data = true;
+      buffer += start;
+      ASSERT(bytes_count == array_len);
+    } else {
+      ThrowIfError(Dart_TypedDataReleaseData(buffer_obj));
+    }
+  }
+
+  if (!is_byte_data) {
+    buffer = Dart_ScopeAllocate(length);
+  }
+
   int64_t bytes_read = file->Read(reinterpret_cast<void*>(buffer), length);
   if (bytes_read >= 0) {
-    result = Dart_ListSetAsBytes(buffer_obj, start, buffer, bytes_read);
+    if (is_byte_data) {
+      result = Dart_TypedDataReleaseData(buffer_obj);
+    } else {
+      result = Dart_ListSetAsBytes(buffer_obj, start, buffer, bytes_read);
+    }
     if (Dart_IsError(result)) {
       Dart_SetReturnValue(args, result);
     } else {
diff --git a/tests/standalone/io/file_test.dart b/tests/standalone/io/file_test.dart
index 5491ea8..07ca4a5 100644
--- a/tests/standalone/io/file_test.dart
+++ b/tests/standalone/io/file_test.dart
@@ -825,19 +825,20 @@
     asyncTestDone("testReadInto");
   }
 
-  static void testReadIntoSync() {
+  static void testReadIntoSync(
+      List<int> Function(int length, int fill) listFactory) {
     File file = new File(tempDirectory.path + "/out_read_into_sync");
 
     var openedFile = file.openSync(mode: FileMode.write);
     openedFile.writeFromSync(const [1, 2, 3]);
 
     openedFile.setPositionSync(0);
-    var list = [-1, -1, -1];
+    var list = listFactory(3, 49);
     Expect.equals(3, openedFile.readIntoSync(list));
     Expect.listEquals([1, 2, 3], list);
 
     read(start, end, length, expected) {
-      var list = [-1, -1, -1];
+      var list = listFactory(3, 49);
       openedFile.setPositionSync(0);
       Expect.equals(length, openedFile.readIntoSync(list, start, end));
       Expect.listEquals(expected, list);
@@ -845,11 +846,11 @@
     }
 
     read(0, 3, 3, [1, 2, 3]);
-    read(0, 2, 2, [1, 2, -1]);
-    read(1, 2, 1, [-1, 1, -1]);
-    read(1, 3, 2, [-1, 1, 2]);
-    read(2, 3, 1, [-1, -1, 1]);
-    read(0, 0, 0, [-1, -1, -1]);
+    read(0, 2, 2, [1, 2, 49]);
+    read(1, 2, 1, [49, 1, 49]);
+    read(1, 3, 2, [49, 1, 2]);
+    read(2, 3, 1, [49, 49, 1]);
+    read(0, 0, 0, [49, 49, 49]);
 
     openedFile.closeSync();
   }
@@ -1764,7 +1765,13 @@
       testTruncate();
       testTruncateSync();
       testReadInto();
-      testReadIntoSync();
+      testReadIntoSync(List<int>.filled);
+      // readIntoSync has an optimized code path for UInt8List.
+      testReadIntoSync(
+          (length, fill) => Uint8List(length)..fillRange(0, length, fill));
+      // readIntoSync should worked with typed data that is not uint8.
+      testReadIntoSync(
+          (length, fill) => Uint16List(length)..fillRange(0, length, fill));
       testWriteFrom();
       testWriteFromSync();
       testCloseException();
diff --git a/tests/standalone_2/io/file_test.dart b/tests/standalone_2/io/file_test.dart
index 3f58df2..19aee21 100644
--- a/tests/standalone_2/io/file_test.dart
+++ b/tests/standalone_2/io/file_test.dart
@@ -829,19 +829,20 @@
     asyncTestDone("testReadInto");
   }
 
-  static void testReadIntoSync() {
+  static void testReadIntoSync(
+      List<int> Function(int length, int fill) listFactory) {
     File file = new File(tempDirectory.path + "/out_read_into_sync");
 
     var openedFile = file.openSync(mode: FileMode.write);
     openedFile.writeFromSync(const [1, 2, 3]);
 
     openedFile.setPositionSync(0);
-    var list = <int>[null, null, null];
+    var list = listFactory(3, 49);
     Expect.equals(3, openedFile.readIntoSync(list));
     Expect.listEquals([1, 2, 3], list);
 
     read(start, end, length, expected) {
-      var list = <int>[null, null, null];
+      var list = listFactory(3, 49);
       openedFile.setPositionSync(0);
       Expect.equals(length, openedFile.readIntoSync(list, start, end));
       Expect.listEquals(expected, list);
@@ -849,11 +850,11 @@
     }
 
     read(0, 3, 3, [1, 2, 3]);
-    read(0, 2, 2, [1, 2, null]);
-    read(1, 2, 1, [null, 1, null]);
-    read(1, 3, 2, [null, 1, 2]);
-    read(2, 3, 1, [null, null, 1]);
-    read(0, 0, 0, [null, null, null]);
+    read(0, 2, 2, [1, 2, 49]);
+    read(1, 2, 1, [49, 1, 49]);
+    read(1, 3, 2, [49, 1, 2]);
+    read(2, 3, 1, [49, 49, 1]);
+    read(0, 0, 0, [49, 49, 49]);
 
     openedFile.closeSync();
   }
@@ -1765,7 +1766,13 @@
       testTruncate();
       testTruncateSync();
       testReadInto();
-      testReadIntoSync();
+      testReadIntoSync((length, fill) => List<int>.filled(length, fill));
+      // readIntoSync has an optimized code path for UInt8List.
+      testReadIntoSync(
+          (length, fill) => Uint8List(length)..fillRange(0, length, fill));
+      // readIntoSync should worked with typed data that is not uint8.
+      testReadIntoSync(
+          (length, fill) => Uint16List(length)..fillRange(0, length, fill));
       testWriteFrom();
       testWriteFromSync();
       testCloseException();