Version 2.16.0-4.0.dev

Merge commit 'bbff4d95893fc8672df02bdbe3ea74f0fd0534f5' into 'dev'
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
index bc5786d..7c5152d 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
@@ -126,7 +126,8 @@
     var compilerOptions = SharedCompilerOptions(
         replCompile: true,
         moduleName: moduleName,
-        soundNullSafety: setup.soundNullSafety);
+        soundNullSafety: setup.soundNullSafety,
+        emitDebugMetadata: true);
     var coreTypes = compiler.getCoreTypes();
 
     final importToSummary = Map<Library, Component>.identity();
@@ -145,10 +146,10 @@
     var code = jsProgramToCode(
       module,
       setup.moduleFormat,
-      inlineSourceMap: true,
-      buildSourceMap: true,
-      emitDebugMetadata: true,
-      emitDebugSymbols: true,
+      inlineSourceMap: compilerOptions.inlineSourceMap,
+      buildSourceMap: compilerOptions.sourceMap,
+      emitDebugMetadata: compilerOptions.emitDebugMetadata,
+      emitDebugSymbols: compilerOptions.emitDebugSymbols,
       jsUrl: '$output',
       mapUrl: '$output.map',
       compiler: kernel2jsCompiler,
diff --git a/runtime/include/dart_tools_api.h b/runtime/include/dart_tools_api.h
index f36ec6b..6d02750 100644
--- a/runtime/include/dart_tools_api.h
+++ b/runtime/include/dart_tools_api.h
@@ -363,6 +363,37 @@
  */
 
 /**
+ * Enable tracking of specified timeline category. This is operational
+ * only when systrace timeline functionality is turned on.
+ *
+ * \param categories A comma seperated list of categories that need to
+ *   be enabled, the categories are
+ *   "all" : All categories
+ *   "API" - Execution of Dart C API functions
+ *   "Compiler" - Execution of Dart JIT compiler
+ *   "CompilerVerbose" - More detailed Execution of Dart JIT compiler
+ *   "Dart" - Execution of Dart code
+ *   "Debugger" - Execution of Dart debugger
+ *   "Embedder" - Execution of Dart embedder code
+ *   "GC" - Execution of Dart Garbage Collector
+ *   "Isolate" - Dart Isolate lifecycle execution
+ *   "VM" - Excution in Dart VM runtime code
+ *   "" - None
+ *
+ *  When "all" is specified all the categories are enabled.
+ *  When a comma seperated list of categories is specified, the categories
+ *   that are specified will be enabled and the rest will be disabled. 
+ *  When "" is specified all the categories are disabled.
+ *  The category names are case sensitive.
+ *  eg:  Dart_EnableTimelineCategory("all");
+ *       Dart_EnableTimelineCategory("GC,API,Isolate");
+ *       Dart_EnableTimelineCategory("GC,Debugger,Dart");
+ *
+ * \return True if the categories were successfully enabled, False otherwise.
+ */
+DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories);
+
+/**
  * Returns a timestamp in microseconds. This timestamp is suitable for
  * passing into the timeline system, and uses the same monotonic clock
  * as dart:developer's Timeline.now.
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index e13d55f..85e22b5 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -2300,7 +2300,7 @@
            compiler::FieldAddress(
                field_reg, Field::guarded_list_length_in_object_offset_offset()),
            compiler::kByte);
-    __ LoadCompressed(
+    __ LoadCompressedSmi(
         length_reg,
         compiler::FieldAddress(field_reg, Field::guarded_list_length_offset()));
 
@@ -2311,7 +2311,7 @@
     // value's class matches guarded class id of the field.
     // offset_reg contains offset already corrected by -kHeapObjectTag that is
     // why we use Address instead of FieldAddress.
-    __ ldr(TMP, compiler::Address(value_reg, offset_reg));
+    __ LoadCompressedSmi(TMP, compiler::Address(value_reg, offset_reg));
     __ CompareObjectRegisters(length_reg, TMP);
 
     if (deopt == NULL) {
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 97f1552..4ca35a6 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -2288,7 +2288,7 @@
         offset_reg,
         compiler::FieldAddress(
             field_reg, Field::guarded_list_length_in_object_offset_offset()));
-    __ LoadCompressed(
+    __ LoadCompressedSmi(
         length_reg,
         compiler::FieldAddress(field_reg, Field::guarded_list_length_offset()));
 
diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
index 7b9bac9..2f8ac3b 100644
--- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
+++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
@@ -19,6 +19,14 @@
 #define Z (zone_)
 #define IG (thread_->isolate_group())
 
+static bool SupportsCoverage() {
+#if defined(PRODUCT)
+  return false;
+#else
+  return !CompilerState::Current().is_aot();
+#endif
+}
+
 Fragment& Fragment::operator+=(const Fragment& other) {
   if (entry == NULL) {
     entry = other.entry;
@@ -1138,25 +1146,11 @@
   return call_hook;
 }
 
-static bool SupportsCoverage() {
-#if defined(PRODUCT)
-  return false;
-#else
-  return !CompilerState::Current().is_aot();
-#endif
-}
-
 Fragment BaseFlowGraphBuilder::ClosureCall(TokenPosition position,
                                            intptr_t type_args_len,
                                            intptr_t argument_count,
                                            const Array& argument_names) {
-  Fragment result;
-
-  if (SupportsCoverage()) {
-    const intptr_t coverage_index = GetCoverageIndexFor(position);
-    result <<= new (Z) RecordCoverageInstr(coverage_array(), coverage_index,
-                                           InstructionSource(position));
-  }
+  Fragment result = RecordCoverage(position);
   const intptr_t total_count =
       (type_args_len > 0 ? 1 : 0) + argument_count +
       /*closure (bare instructions) or function (otherwise)*/ 1;
@@ -1249,6 +1243,16 @@
   return Fragment(instr);
 }
 
+Fragment BaseFlowGraphBuilder::RecordCoverage(TokenPosition position) {
+  Fragment instructions;
+  if (SupportsCoverage()) {
+    const intptr_t coverage_index = GetCoverageIndexFor(position);
+    instructions <<= new (Z) RecordCoverageInstr(
+        coverage_array(), coverage_index, InstructionSource(position));
+  }
+  return instructions;
+}
+
 intptr_t BaseFlowGraphBuilder::GetCoverageIndexFor(TokenPosition token_pos) {
   if (coverage_array_.IsNull()) {
     // We have not yet created coverage_array, this is the first time
diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
index ee7e7ce..b0597b8 100644
--- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h
+++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
@@ -471,6 +471,9 @@
   // Pops double value and applies unary math operation.
   Fragment MathUnary(MathUnaryInstr::MathUnaryKind kind);
 
+  // Records coverage for this position, if the current VM mode supports it.
+  Fragment RecordCoverage(TokenPosition position);
+
   // Returns whether this function has a saved arguments descriptor array.
   bool has_saved_args_desc_array() {
     return function_.HasSavedArgumentsDescriptor();
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 6bf2d87..08e04f9 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -1870,6 +1870,10 @@
       type, variable->name(), AssertAssignableInstr::kParameterCheck);
 }
 
+Fragment StreamingFlowGraphBuilder::RecordCoverage(TokenPosition position) {
+  return flow_graph_builder_->RecordCoverage(position);
+}
+
 Fragment StreamingFlowGraphBuilder::EnterScope(
     intptr_t kernel_offset,
     const LocalScope** scope /* = nullptr */) {
@@ -4338,6 +4342,7 @@
       ReadPosition();  // read condition end offset.
 
   instructions += EvaluateAssertion();
+  instructions += RecordCoverage(condition_start_offset);
   instructions += CheckBoolean(condition_start_offset);
   instructions += Constant(Bool::True());
   instructions += BranchIfEqual(&then, &otherwise, false);
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index 3cd6257..0f40918 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -254,6 +254,7 @@
   Fragment BuildImplicitClosureCreation(const Function& target);
   Fragment CheckBoolean(TokenPosition position);
   Fragment CheckArgumentType(LocalVariable* variable, const AbstractType& type);
+  Fragment RecordCoverage(TokenPosition position);
   Fragment EnterScope(intptr_t kernel_offset,
                       const LocalScope** scope = nullptr);
   Fragment ExitScope(intptr_t kernel_offset);
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 54196b7..1ea25d2 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -150,8 +150,7 @@
   __ StoreToOffset(R2, THR, target::Thread::vm_tag_offset());
 
   // Mark that the thread has not exited generated Dart code.
-  __ LoadImmediate(R2, 0);
-  __ StoreToOffset(R2, THR, target::Thread::exit_through_ffi_offset());
+  __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
 
   // Reset exit frame information in Isolate's mutator thread structure.
   __ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
@@ -722,8 +721,7 @@
   __ StoreToOffset(R2, THR, target::Thread::vm_tag_offset());
 
   // Mark that the thread has not exited generated Dart code.
-  __ LoadImmediate(R2, 0);
-  __ StoreToOffset(R2, THR, target::Thread::exit_through_ffi_offset());
+  __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
 
   // Reset exit frame information in Isolate's mutator thread structure.
   __ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
@@ -1389,8 +1387,7 @@
 
   __ LoadFromOffset(R6, THR, target::Thread::exit_through_ffi_offset());
   __ Push(R6);
-  __ LoadImmediate(R6, 0);
-  __ StoreToOffset(R6, THR, target::Thread::exit_through_ffi_offset());
+  __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
 
   __ LoadFromOffset(R6, THR, target::Thread::top_exit_frame_info_offset());
   __ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 88dc441..437ce6f 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -6272,6 +6272,20 @@
 #endif
 }
 
+DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories) {
+#if defined(SUPPORT_TIMELINE)
+  bool result = false;
+  if (categories != nullptr) {
+    char* carray = Utils::SCreate("[%s]", categories);
+    result = Service::EnableTimelineStreams(carray);
+    free(carray);
+  }
+  return result;
+#else
+  return false;
+#endif
+}
+
 DART_EXPORT int64_t Dart_TimelineGetMicros() {
   return OS::GetCurrentMonotonicMicros();
 }
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index 7074ded..5a9050a 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -9323,6 +9323,84 @@
   EXPECT_EQ(frequency1, frequency2);
 }
 
+TEST_CASE(DartAPI_TimelineCategories) {
+  bool result;
+  {
+    result = Dart_SetEnabledTimelineCategory("all");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+#define TIMELINE_STREAM_CHECK(name, fuchsia_name)                              \
+  EXPECT_SUBSTRING(#name, js_str);
+    TIMELINE_STREAM_LIST(TIMELINE_STREAM_CHECK)
+#undef TIMELINE_STREAM_CHECK
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory(nullptr);
+    EXPECT_EQ(false, result);
+    result = Dart_SetEnabledTimelineCategory("GC,api,compiler");
+    EXPECT_EQ(false, result);
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory("GC,API,Compiler");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+    EXPECT_SUBSTRING("GC", js_str);
+    EXPECT_SUBSTRING("API", js_str);
+    EXPECT_SUBSTRING("Compiler", js_str);
+    EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+    EXPECT_NOTSUBSTRING("Debugger", js_str);
+    EXPECT_NOTSUBSTRING("Embedder", js_str);
+    EXPECT_NOTSUBSTRING("Isolate", js_str);
+    EXPECT_NOTSUBSTRING("VM", js_str);
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory("Isolate");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+    EXPECT_NOTSUBSTRING("GC", js_str);
+    EXPECT_NOTSUBSTRING("API", js_str);
+    EXPECT_NOTSUBSTRING("Compiler", js_str);
+    EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+    EXPECT_NOTSUBSTRING("Debugger", js_str);
+    EXPECT_NOTSUBSTRING("Embedder", js_str);
+    EXPECT_SUBSTRING("Isolate", js_str);
+    EXPECT_NOTSUBSTRING("VM", js_str);
+  }
+
+  {
+    result = Dart_SetEnabledTimelineCategory("");
+    EXPECT_EQ(true, result);
+    JSONStream js;
+    JSONObject obj(&js);
+    JSONArray jstream(&obj, "available");
+    Timeline::PrintFlagsToJSONArray(&jstream);
+    const char* js_str = js.ToCString();
+    EXPECT_NOTSUBSTRING("GC", js_str);
+    EXPECT_NOTSUBSTRING("API", js_str);
+    EXPECT_NOTSUBSTRING("Compiler", js_str);
+    EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+    EXPECT_NOTSUBSTRING("Debugger", js_str);
+    EXPECT_NOTSUBSTRING("Embedder", js_str);
+    EXPECT_NOTSUBSTRING("Isolate", js_str);
+    EXPECT_NOTSUBSTRING("VM", js_str);
+  }
+}
+
 static void HintFreedNative(Dart_NativeArguments args) {
   int64_t size = 0;
   EXPECT_VALID(Dart_GetNativeIntegerArgument(args, 0, &size));
diff --git a/runtime/vm/heap/compactor.cc b/runtime/vm/heap/compactor.cc
index 7311ecf..2051b0c 100644
--- a/runtime/vm/heap/compactor.cc
+++ b/runtime/vm/heap/compactor.cc
@@ -117,21 +117,30 @@
   forwarding_page_ = reinterpret_cast<ForwardingPage*>(object_end_);
 }
 
+struct Partition {
+  OldPage* head;
+  OldPage* tail;
+};
+
 class CompactorTask : public ThreadPool::Task {
  public:
   CompactorTask(IsolateGroup* isolate_group,
                 GCCompactor* compactor,
                 ThreadBarrier* barrier,
+                RelaxedAtomic<intptr_t>* next_planning_task,
+                RelaxedAtomic<intptr_t>* next_sliding_task,
                 RelaxedAtomic<intptr_t>* next_forwarding_task,
-                OldPage* head,
-                OldPage** tail,
+                intptr_t num_tasks,
+                Partition* partitions,
                 FreeList* freelist)
       : isolate_group_(isolate_group),
         compactor_(compactor),
         barrier_(barrier),
+        next_planning_task_(next_planning_task),
+        next_sliding_task_(next_sliding_task),
         next_forwarding_task_(next_forwarding_task),
-        head_(head),
-        tail_(tail),
+        num_tasks_(num_tasks),
+        partitions_(partitions),
         freelist_(freelist),
         free_page_(NULL),
         free_current_(0),
@@ -150,9 +159,11 @@
   IsolateGroup* isolate_group_;
   GCCompactor* compactor_;
   ThreadBarrier* barrier_;
+  RelaxedAtomic<intptr_t>* next_planning_task_;
+  RelaxedAtomic<intptr_t>* next_sliding_task_;
   RelaxedAtomic<intptr_t>* next_forwarding_task_;
-  OldPage* head_;
-  OldPage** tail_;
+  intptr_t num_tasks_;
+  Partition* partitions_;
   FreeList* freelist_;
   OldPage* free_page_;
   uword free_current_;
@@ -184,8 +195,8 @@
   if (num_pages < num_tasks) {
     num_tasks = num_pages;
   }
-  OldPage** heads = new OldPage*[num_tasks];
-  OldPage** tails = new OldPage*[num_tasks];
+
+  Partition* partitions = new Partition[num_tasks];
 
   {
     const intptr_t pages_per_task = num_pages / num_tasks;
@@ -195,8 +206,8 @@
     OldPage* prev = NULL;
     while (task_index < num_tasks) {
       if (page_index % pages_per_task == 0) {
-        heads[task_index] = page;
-        tails[task_index] = NULL;
+        partitions[task_index].head = page;
+        partitions[task_index].tail = NULL;
         if (prev != NULL) {
           prev->set_next(NULL);
         }
@@ -232,27 +243,31 @@
                                    page->object_end() - page->object_start());
 
         // The compactor slides down: add the empty pages to the beginning.
-        page->set_next(heads[task_index]);
-        heads[task_index] = page;
+        page->set_next(partitions[task_index].head);
+        partitions[task_index].head = page;
       }
     }
   }
 
   {
-    ThreadBarrier* barrier = new ThreadBarrier(num_tasks, num_tasks);
+    ThreadBarrier* barrier = new ThreadBarrier(num_tasks, 1);
+    RelaxedAtomic<intptr_t> next_planning_task = {0};
+    RelaxedAtomic<intptr_t> next_sliding_task = {0};
     RelaxedAtomic<intptr_t> next_forwarding_task = {0};
 
     for (intptr_t task_index = 0; task_index < num_tasks; task_index++) {
       if (task_index < (num_tasks - 1)) {
         // Begin compacting on a helper thread.
         Dart::thread_pool()->Run<CompactorTask>(
-            thread()->isolate_group(), this, barrier, &next_forwarding_task,
-            heads[task_index], &tails[task_index], freelist);
+            thread()->isolate_group(), this, barrier, &next_planning_task,
+            &next_sliding_task, &next_forwarding_task, num_tasks, partitions,
+            freelist);
       } else {
         // Last worker is the main thread.
         CompactorTask task(thread()->isolate_group(), this, barrier,
-                           &next_forwarding_task, heads[task_index],
-                           &tails[task_index], freelist);
+                           &next_planning_task, &next_sliding_task,
+                           &next_forwarding_task, num_tasks, partitions,
+                           freelist);
         task.RunEnteredIsolateGroup();
         barrier->Sync();
         barrier->Release();
@@ -290,7 +305,7 @@
   }
 
   for (intptr_t task_index = 0; task_index < num_tasks; task_index++) {
-    ASSERT(tails[task_index] != NULL);
+    ASSERT(partitions[task_index].tail != NULL);
   }
 
   {
@@ -305,7 +320,7 @@
 
     // Free empty pages.
     for (intptr_t task_index = 0; task_index < num_tasks; task_index++) {
-      OldPage* page = tails[task_index]->next();
+      OldPage* page = partitions[task_index].tail->next();
       while (page != NULL) {
         OldPage* next = page->next();
         heap_->old_space()->IncreaseCapacityInWordsLocked(
@@ -317,18 +332,22 @@
 
     // Re-join the heap.
     for (intptr_t task_index = 0; task_index < num_tasks - 1; task_index++) {
-      tails[task_index]->set_next(heads[task_index + 1]);
+      partitions[task_index].tail->set_next(partitions[task_index + 1].head);
     }
-    tails[num_tasks - 1]->set_next(NULL);
-    heap_->old_space()->pages_ = pages = heads[0];
-    heap_->old_space()->pages_tail_ = tails[num_tasks - 1];
+    partitions[num_tasks - 1].tail->set_next(NULL);
+    heap_->old_space()->pages_ = pages = partitions[0].head;
+    heap_->old_space()->pages_tail_ = partitions[num_tasks - 1].tail;
 
-    delete[] heads;
-    delete[] tails;
+    delete[] partitions;
   }
 }
 
 void CompactorTask::Run() {
+  if (!barrier_->TryEnter()) {
+    barrier_->Release();
+    return;
+  }
+
   bool result =
       Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kCompactorTask,
                                         /*bypass_safepoint=*/true);
@@ -348,26 +367,34 @@
   Thread* thread = Thread::Current();
 #endif
   {
-    {
-      TIMELINE_FUNCTION_GC_DURATION(thread, "Plan");
-      free_page_ = head_;
-      free_current_ = free_page_->object_start();
-      free_end_ = free_page_->object_end();
+    while (true) {
+      intptr_t planning_task = next_planning_task_->fetch_add(1u);
+      if (planning_task >= num_tasks_) break;
 
-      for (OldPage* page = head_; page != NULL; page = page->next()) {
+      TIMELINE_FUNCTION_GC_DURATION(thread, "Plan");
+      OldPage* head = partitions_[planning_task].head;
+      free_page_ = head;
+      free_current_ = head->object_start();
+      free_end_ = head->object_end();
+
+      for (OldPage* page = head; page != NULL; page = page->next()) {
         PlanPage(page);
       }
     }
 
     barrier_->Sync();
 
-    {
-      TIMELINE_FUNCTION_GC_DURATION(thread, "Slide");
-      free_page_ = head_;
-      free_current_ = free_page_->object_start();
-      free_end_ = free_page_->object_end();
+    while (true) {
+      intptr_t sliding_task = next_sliding_task_->fetch_add(1u);
+      if (sliding_task >= num_tasks_) break;
 
-      for (OldPage* page = head_; page != NULL; page = page->next()) {
+      TIMELINE_FUNCTION_GC_DURATION(thread, "Slide");
+      OldPage* head = partitions_[sliding_task].head;
+      free_page_ = head;
+      free_current_ = head->object_start();
+      free_end_ = head->object_end();
+
+      for (OldPage* page = head; page != NULL; page = page->next()) {
         SlidePage(page);
       }
 
@@ -379,7 +406,7 @@
       }
 
       ASSERT(free_page_ != NULL);
-      *tail_ = free_page_;  // Last live page.
+      partitions_[sliding_task].tail = free_page_;  // Last live page.
     }
 
     // Heap: Regular pages already visited during sliding. Code and image pages
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index b1e4939..f9616f3 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -482,6 +482,11 @@
         ShouldLoadVmServiceBit::update(value, isolate_group_flags_);
   }
 
+  void set_asserts(bool value) {
+    isolate_group_flags_ =
+        EnableAssertsBit::update(value, isolate_group_flags_);
+  }
+
 #if !defined(PRODUCT)
 #if !defined(DART_PRECOMPILED_RUNTIME)
   bool HasAttemptedReload() const {
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index a75737e..b70dea0 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -72,6 +72,231 @@
             "Print a message when an isolate is paused but there is no "
             "debugger attached.");
 
+static void PrintInvalidParamError(JSONStream* js, const char* param) {
+#if !defined(PRODUCT)
+  js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
+                 param, js->LookupParam(param));
+#endif
+}
+
+// TODO(johnmccutchan): Split into separate file and write unit tests.
+class MethodParameter {
+ public:
+  MethodParameter(const char* name, bool required)
+      : name_(name), required_(required) {}
+
+  virtual ~MethodParameter() {}
+
+  virtual bool Validate(const char* value) const { return true; }
+
+  virtual bool ValidateObject(const Object& value) const { return true; }
+
+  const char* name() const { return name_; }
+
+  bool required() const { return required_; }
+
+  virtual void PrintError(const char* name,
+                          const char* value,
+                          JSONStream* js) const {
+    PrintInvalidParamError(js, name);
+  }
+
+  virtual void PrintErrorObject(const char* name,
+                                const Object& value,
+                                JSONStream* js) const {
+    PrintInvalidParamError(js, name);
+  }
+
+ private:
+  const char* name_;
+  bool required_;
+};
+
+class NoSuchParameter : public MethodParameter {
+ public:
+  explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
+
+  virtual bool Validate(const char* value) const { return (value == NULL); }
+
+  virtual bool ValidateObject(const Object& value) const {
+    return value.IsNull();
+  }
+};
+
+#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
+#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
+#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
+#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
+
+class EnumListParameter : public MethodParameter {
+ public:
+  EnumListParameter(const char* name, bool required, const char* const* enums)
+      : MethodParameter(name, required), enums_(enums) {}
+
+  virtual bool Validate(const char* value) const {
+    return ElementCount(value) >= 0;
+  }
+
+  const char** Parse(char* value) const {
+    const char* kJsonChars = " \t\r\n[,]";
+
+    // Make a writeable copy of the value.
+    intptr_t element_count = ElementCount(value);
+    if (element_count < 0) {
+      return nullptr;
+    }
+    intptr_t element_pos = 0;
+
+    // Allocate our element array.  +1 for NULL terminator.
+    // The caller is reponsible for deleting this memory.
+    char** elements = new char*[element_count + 1];
+    elements[element_count] = NULL;
+
+    // Parse the string destructively.  Build the list of elements.
+    while (element_pos < element_count) {
+      // Skip to the next element.
+      value += strspn(value, kJsonChars);
+
+      intptr_t len = strcspn(value, kJsonChars);
+      ASSERT(len > 0);  // We rely on the parameter being validated already.
+      value[len] = '\0';
+      elements[element_pos++] = value;
+
+      // Advance.  +1 for null terminator.
+      value += (len + 1);
+    }
+    return const_cast<const char**>(elements);
+  }
+
+ private:
+  // For now observatory enums are ascii letters plus underscore.
+  static bool IsEnumChar(char c) {
+    return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
+            (c == '_'));
+  }
+
+  // Returns number of elements in the list.  -1 on parse error.
+  intptr_t ElementCount(const char* value) const {
+    const char* kJsonWhitespaceChars = " \t\r\n";
+    if (value == NULL) {
+      return -1;
+    }
+    const char* cp = value;
+    cp += strspn(cp, kJsonWhitespaceChars);
+    if (*cp++ != '[') {
+      // Missing initial [.
+      return -1;
+    }
+    bool closed = false;
+    bool element_allowed = true;
+    intptr_t element_count = 0;
+    while (true) {
+      // Skip json whitespace.
+      cp += strspn(cp, kJsonWhitespaceChars);
+      switch (*cp) {
+        case '\0':
+          return closed ? element_count : -1;
+        case ']':
+          closed = true;
+          cp++;
+          break;
+        case ',':
+          if (element_allowed) {
+            return -1;
+          }
+          element_allowed = true;
+          cp++;
+          break;
+        default:
+          if (!element_allowed) {
+            return -1;
+          }
+          bool valid_enum = false;
+          const char* id_start = cp;
+          while (IsEnumChar(*cp)) {
+            cp++;
+          }
+          if (cp == id_start) {
+            // Empty identifier, something like this [,].
+            return -1;
+          }
+          intptr_t id_len = cp - id_start;
+          if (enums_ != NULL) {
+            for (intptr_t i = 0; enums_[i] != NULL; i++) {
+              intptr_t len = strlen(enums_[i]);
+              if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
+                element_count++;
+                valid_enum = true;
+                element_allowed = false;  // we need a comma first.
+                break;
+              }
+            }
+          }
+          if (!valid_enum) {
+            return -1;
+          }
+          break;
+      }
+    }
+  }
+
+  const char* const* enums_;
+};
+
+#if defined(SUPPORT_TIMELINE)
+static const char* const timeline_streams_enum_names[] = {
+    "all",
+#define DEFINE_NAME(name, unused) #name,
+    TIMELINE_STREAM_LIST(DEFINE_NAME)
+#undef DEFINE_NAME
+        NULL};
+
+static const MethodParameter* const set_vm_timeline_flags_params[] = {
+    NO_ISOLATE_PARAMETER,
+    new EnumListParameter("recordedStreams",
+                          false,
+                          timeline_streams_enum_names),
+    NULL,
+};
+
+static bool HasStream(const char** recorded_streams, const char* stream) {
+  while (*recorded_streams != NULL) {
+    if ((strstr(*recorded_streams, "all") != NULL) ||
+        (strstr(*recorded_streams, stream) != NULL)) {
+      return true;
+    }
+    recorded_streams++;
+  }
+  return false;
+}
+
+bool Service::EnableTimelineStreams(char* categories_list) {
+  const EnumListParameter* recorded_streams_param =
+      static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
+  const char** streams = recorded_streams_param->Parse(categories_list);
+  if (streams == nullptr) {
+    return false;
+  }
+
+#define SET_ENABLE_STREAM(name, unused)                                        \
+  Timeline::SetStream##name##Enabled(HasStream(streams, #name));
+  TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
+#undef SET_ENABLE_STREAM
+
+  delete[] streams;
+
+#if !defined(PRODUCT)
+  // Notify clients that the set of subscribed streams has been updated.
+  if (Service::timeline_stream.enabled()) {
+    ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
+    Service::HandleEvent(&event);
+  }
+#endif
+
+  return true;
+}
+#endif  // defined(SUPPORT_TIMELINE)
+
 #ifndef PRODUCT
 // The name of this of this vm as reported by the VM service protocol.
 static char* vm_name = NULL;
@@ -220,20 +445,6 @@
   return object.ptr();
 }
 
-static void PrintMissingParamError(JSONStream* js, const char* param) {
-  js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
-                 param);
-}
-
-static void PrintInvalidParamError(JSONStream* js, const char* param) {
-  js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
-                 param, js->LookupParam(param));
-}
-
-static void PrintUnrecognizedMethodError(JSONStream* js) {
-  js->PrintError(kMethodNotFound, NULL);
-}
-
 static void PrintSuccess(JSONStream* js) {
   JSONObject jsobj(js);
   jsobj.AddProperty("type", "Success");
@@ -430,39 +641,6 @@
   return class_table->At(cid);
 }
 
-// TODO(johnmccutchan): Split into separate file and write unit tests.
-class MethodParameter {
- public:
-  MethodParameter(const char* name, bool required)
-      : name_(name), required_(required) {}
-
-  virtual ~MethodParameter() {}
-
-  virtual bool Validate(const char* value) const { return true; }
-
-  virtual bool ValidateObject(const Object& value) const { return true; }
-
-  const char* name() const { return name_; }
-
-  bool required() const { return required_; }
-
-  virtual void PrintError(const char* name,
-                          const char* value,
-                          JSONStream* js) const {
-    PrintInvalidParamError(js, name);
-  }
-
-  virtual void PrintErrorObject(const char* name,
-                                const Object& value,
-                                JSONStream* js) const {
-    PrintInvalidParamError(js, name);
-  }
-
- private:
-  const char* name_;
-  bool required_;
-};
-
 class DartStringParameter : public MethodParameter {
  public:
   DartStringParameter(const char* name, bool required)
@@ -483,17 +661,6 @@
   }
 };
 
-class NoSuchParameter : public MethodParameter {
- public:
-  explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
-
-  virtual bool Validate(const char* value) const { return (value == NULL); }
-
-  virtual bool ValidateObject(const Object& value) const {
-    return value.IsNull();
-  }
-};
-
 class BoolParameter : public MethodParameter {
  public:
   BoolParameter(const char* name, bool required)
@@ -632,11 +799,6 @@
   }
 };
 
-#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
-#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
-#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
-#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
-
 class EnumParameter : public MethodParameter {
  public:
   EnumParameter(const char* name, bool required, const char* const* enums)
@@ -673,118 +835,6 @@
   return values[i];
 }
 
-class EnumListParameter : public MethodParameter {
- public:
-  EnumListParameter(const char* name, bool required, const char* const* enums)
-      : MethodParameter(name, required), enums_(enums) {}
-
-  virtual bool Validate(const char* value) const {
-    return ElementCount(value) >= 0;
-  }
-
-  const char** Parse(Zone* zone, const char* value_in) const {
-    const char* kJsonChars = " \t\r\n[,]";
-
-    // Make a writeable copy of the value.
-    char* value = zone->MakeCopyOfString(value_in);
-    intptr_t element_count = ElementCount(value);
-    intptr_t element_pos = 0;
-
-    // Allocate our element array.  +1 for NULL terminator.
-    char** elements = zone->Alloc<char*>(element_count + 1);
-    elements[element_count] = NULL;
-
-    // Parse the string destructively.  Build the list of elements.
-    while (element_pos < element_count) {
-      // Skip to the next element.
-      value += strspn(value, kJsonChars);
-
-      intptr_t len = strcspn(value, kJsonChars);
-      ASSERT(len > 0);  // We rely on the parameter being validated already.
-      value[len] = '\0';
-      elements[element_pos++] = value;
-
-      // Advance.  +1 for null terminator.
-      value += (len + 1);
-    }
-    return const_cast<const char**>(elements);
-  }
-
- private:
-  // For now observatory enums are ascii letters plus underscore.
-  static bool IsEnumChar(char c) {
-    return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
-            (c == '_'));
-  }
-
-  // Returns number of elements in the list.  -1 on parse error.
-  intptr_t ElementCount(const char* value) const {
-    const char* kJsonWhitespaceChars = " \t\r\n";
-    if (value == NULL) {
-      return -1;
-    }
-    const char* cp = value;
-    cp += strspn(cp, kJsonWhitespaceChars);
-    if (*cp++ != '[') {
-      // Missing initial [.
-      return -1;
-    }
-    bool closed = false;
-    bool element_allowed = true;
-    intptr_t element_count = 0;
-    while (true) {
-      // Skip json whitespace.
-      cp += strspn(cp, kJsonWhitespaceChars);
-      switch (*cp) {
-        case '\0':
-          return closed ? element_count : -1;
-        case ']':
-          closed = true;
-          cp++;
-          break;
-        case ',':
-          if (element_allowed) {
-            return -1;
-          }
-          element_allowed = true;
-          cp++;
-          break;
-        default:
-          if (!element_allowed) {
-            return -1;
-          }
-          bool valid_enum = false;
-          const char* id_start = cp;
-          while (IsEnumChar(*cp)) {
-            cp++;
-          }
-          if (cp == id_start) {
-            // Empty identifier, something like this [,].
-            return -1;
-          }
-          intptr_t id_len = cp - id_start;
-          if (enums_ != NULL) {
-            for (intptr_t i = 0; enums_[i] != NULL; i++) {
-              intptr_t len = strlen(enums_[i]);
-              if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
-                element_count++;
-                valid_enum = true;
-                element_allowed = false;  // we need a comma first.
-                break;
-              }
-            }
-          }
-          if (!valid_enum) {
-            return -1;
-          }
-          break;
-      }
-    }
-  }
-
-  const char* const* enums_;
-};
-
 typedef void (*ServiceMethodEntry)(Thread* thread, JSONStream* js);
 
 struct ServiceMethodDescriptor {
@@ -793,6 +843,15 @@
   const MethodParameter* const* parameters;
 };
 
+static void PrintMissingParamError(JSONStream* js, const char* param) {
+  js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
+                 param);
+}
+
+static void PrintUnrecognizedMethodError(JSONStream* js) {
+  js->PrintError(kMethodNotFound, NULL);
+}
+
 // TODO(johnmccutchan): Do we reject unexpected parameters?
 static bool ValidateParameters(const MethodParameter* const* parameters,
                                JSONStream* js) {
@@ -3349,23 +3408,28 @@
     return;
   }
 
-  const char* reports_str = js->LookupParam("reports");
+  char* reports_str = Utils::StrDup(js->LookupParam("reports"));
   const EnumListParameter* reports_parameter =
       static_cast<const EnumListParameter*>(get_source_report_params[1]);
-  const char** reports = reports_parameter->Parse(thread->zone(), reports_str);
+  const char** reports = reports_parameter->Parse(reports_str);
+  const char** riter = reports;
   intptr_t report_set = 0;
-  while (*reports != NULL) {
-    if (strcmp(*reports, SourceReport::kCallSitesStr) == 0) {
+  while (*riter != NULL) {
+    if (strcmp(*riter, SourceReport::kCallSitesStr) == 0) {
       report_set |= SourceReport::kCallSites;
-    } else if (strcmp(*reports, SourceReport::kCoverageStr) == 0) {
+    } else if (strcmp(*riter, SourceReport::kCoverageStr) == 0) {
       report_set |= SourceReport::kCoverage;
-    } else if (strcmp(*reports, SourceReport::kPossibleBreakpointsStr) == 0) {
+    } else if (strcmp(*riter, SourceReport::kPossibleBreakpointsStr) == 0) {
       report_set |= SourceReport::kPossibleBreakpoints;
-    } else if (strcmp(*reports, SourceReport::kProfileStr) == 0) {
+    } else if (strcmp(*riter, SourceReport::kProfileStr) == 0) {
       report_set |= SourceReport::kProfile;
     }
-    reports++;
+    riter++;
   }
+  if (reports != nullptr) {
+    delete[] reports;
+  }
+  free(reports_str);
 
   SourceReport::CompileMode compile_mode = SourceReport::kNoCompile;
   if (BoolParameter::Parse(js->LookupParam("forceCompile"), false)) {
@@ -3803,34 +3867,6 @@
   }
 }
 
-static const char* const timeline_streams_enum_names[] = {
-    "all",
-#define DEFINE_NAME(name, unused) #name,
-    TIMELINE_STREAM_LIST(DEFINE_NAME)
-#undef DEFINE_NAME
-        NULL};
-
-static const MethodParameter* const set_vm_timeline_flags_params[] = {
-    NO_ISOLATE_PARAMETER,
-    new EnumListParameter("recordedStreams",
-                          false,
-                          timeline_streams_enum_names),
-    NULL,
-};
-
-#if defined(SUPPORT_TIMELINE)
-static bool HasStream(const char** recorded_streams, const char* stream) {
-  while (*recorded_streams != NULL) {
-    if ((strstr(*recorded_streams, "all") != NULL) ||
-        (strstr(*recorded_streams, stream) != NULL)) {
-      return true;
-    }
-    recorded_streams++;
-  }
-  return false;
-}
-#endif
-
 static void SetVMTimelineFlags(Thread* thread, JSONStream* js) {
 #if !defined(SUPPORT_TIMELINE)
   PrintSuccess(js);
@@ -3839,23 +3875,9 @@
   ASSERT(isolate != NULL);
   StackZone zone(thread);
 
-  const EnumListParameter* recorded_streams_param =
-      static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
-
-  const char* recorded_streams_str = js->LookupParam("recordedStreams");
-  const char** recorded_streams =
-      recorded_streams_param->Parse(thread->zone(), recorded_streams_str);
-
-#define SET_ENABLE_STREAM(name, unused)                                        \
-  Timeline::SetStream##name##Enabled(HasStream(recorded_streams, #name));
-  TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
-#undef SET_ENABLE_STREAM
-
-  // Notify clients that the set of subscribed streams has been updated.
-  if (Service::timeline_stream.enabled()) {
-    ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
-    Service::HandleEvent(&event);
-  }
+  char* recorded_streams = Utils::StrDup(js->LookupParam("recordedStreams"));
+  Service::EnableTimelineStreams(recorded_streams);
+  free(recorded_streams);
 
   PrintSuccess(js);
 #endif
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index c3fe1f2..f51eb10 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -161,6 +161,10 @@
                         const Instance& id,
                         const Error& error);
 
+  // Enable/Disable timeline categories.
+  // Returns True if the categories were successfully enabled, False otherwise.
+  static bool EnableTimelineStreams(char* categories_list);
+
   // Well-known streams.
   static StreamInfo vm_stream;
   static StreamInfo isolate_stream;
diff --git a/runtime/vm/source_report_test.cc b/runtime/vm/source_report_test.cc
index 00c4c2e..fe21bbe 100644
--- a/runtime/vm/source_report_test.cc
+++ b/runtime/vm/source_report_test.cc
@@ -819,6 +819,51 @@
       buffer);
 }
 
+ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue47017_Assert) {
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
+  const char* kScript =
+      "void foo(Object? bar) {\n"
+      "  assert(bar == null);\n"
+      "}\n"
+      "void main() {\n"
+      "  foo(null);\n"
+      "}\n";
+
+  Library& lib = Library::Handle();
+  const bool old_asserts = IsolateGroup::Current()->asserts();
+  IsolateGroup::Current()->set_asserts(true);
+  lib ^= ExecuteScript(kScript);
+  IsolateGroup::Current()->set_asserts(old_asserts);
+  ASSERT(!lib.IsNull());
+  const Script& script =
+      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
+
+  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
+  JSONStream js;
+  report.PrintJSON(&js, script);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
+  ElideJSONSubstring("libraries", buffer, buffer);
+  EXPECT_STREQ(
+      "{\"type\":\"SourceReport\",\"ranges\":["
+
+      // Foo is hit, and the assert is hit.
+      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":47,\"compiled\":true,"
+      "\"coverage\":{\"hits\":[0,33],\"misses\":[]}},"
+
+      // Main is hit.
+      "{\"scriptIndex\":0,\"startPos\":49,\"endPos\":76,\"compiled\":true,"
+      "\"coverage\":{\"hits\":[49,65],\"misses\":[]}}],"
+
+      // Only one script in the script table.
+      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
+      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
+      buffer);
+}
+
 #endif  // !PRODUCT
 
 }  // namespace dart
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index ab0d62a..386178e 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -1441,7 +1441,9 @@
   JSONArray events(js);
   for (intptr_t i = 0; i < length(); i++) {
     const TimelineEvent* event = At(i);
-    events.AddValue(event);
+    if (event->IsValid()) {
+      events.AddValue(event);
+    }
   }
 }
 #endif
diff --git a/tests/corelib/error_throw_with_stacktrace_test.dart b/tests/corelib/error_throw_with_stacktrace_test.dart
index 026df50..e74c80c 100644
--- a/tests/corelib/error_throw_with_stacktrace_test.dart
+++ b/tests/corelib/error_throw_with_stacktrace_test.dart
@@ -131,7 +131,7 @@
     // Should work with whichever object gets here.
     try {
       Error.throwWithStackTrace(error, systemStack);
-    } on Error catch (e, s) {
+    } catch (e, s) {
       Expect.identical(error, e);
       Expect.equals("$systemStack", "$s");
     }
diff --git a/tests/corelib_2/error_throw_with_stacktrace_test.dart b/tests/corelib_2/error_throw_with_stacktrace_test.dart
index 303b06c..d6c3b59 100644
--- a/tests/corelib_2/error_throw_with_stacktrace_test.dart
+++ b/tests/corelib_2/error_throw_with_stacktrace_test.dart
@@ -132,7 +132,7 @@
     // Should work with whichever object gets here.
     try {
       Error.throwWithStackTrace(error, systemStack);
-    } on Error catch (e, s) {
+    } catch (e, s) {
       Expect.identical(error, e);
       Expect.equals("$systemStack", "$s");
     }
diff --git a/tools/VERSION b/tools/VERSION
index 83662b0..b6a40f8 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 3
+PRERELEASE 4
 PRERELEASE_PATCH 0
\ No newline at end of file