[vm] Create more Function helper methods for code clarity.

Creates HasParent() as a predicate for Function objects with a non-null
parent function and makes IsLocalFunction() specific to local closures
(i.e. explicit closures with non-null parent functions).

Creates HasSavedArgumentsDescriptor() as a predicate to determine when
saved_args_desc() can be used, to avoid the caller having to do explicit
predicate checks for each dispatcher type that contains one.

Also simplifies Function::PrintName, creates different suffixes for a
SyncGenClosureMaker and its associateed SyncGenClosure, adds suffixes
for skipping more than one generated body when printing the parent name,
and changes ArgumentsDescriptor::PrintTo to match the compact syntax
Function::PrintName used for signifying the arguments descriptor for
certain dispatchers.

TEST=pkg/vm_snapshot_analysis/test/instruction_sizes_test

Cq-Include-Trybots: luci.dart.try:app-kernel-linux-release-x64-try,vm-kernel-precomp-linux-release-x64-try,pkg-linux-release-try
Change-Id: I0ac1226d1ffe1d81743b5c6788da170d5a4010c5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/191560
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart b/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart
index ddb2ac4..5347acc 100644
--- a/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart
+++ b/pkg/vm_snapshot_analysis/test/instruction_sizes_test.dart
@@ -50,6 +50,12 @@
   }
 }
 
+class D {
+  static dynamic tornOff() sync* {
+    yield const K(5);
+  }
+}
+
 @pragma('vm:never-inline')
 Function tearOff(dynamic o) {
   return o.tornOff;
@@ -61,6 +67,7 @@
   }
   print(tearOff(args.isEmpty ? A() : B()));
   print(C.tornOff);
+  print(D.tornOff);
 }
 """
 };
@@ -107,6 +114,12 @@
   }
 }
 
+class D {
+  static dynamic tornOff() sync* {
+    yield const K(5);
+  }
+}
+
 @pragma('vm:never-inline')
 Function tearOff(dynamic o) {
   return o.tornOff;
@@ -116,6 +129,7 @@
   // modified
   print(tearOff(args.isEmpty ? A() : B()));
   print(C.tornOff);
+  print(D.tornOff);
 }
 """
 };
@@ -154,6 +168,12 @@
   }
 }
 
+class D {
+  static dynamic tornOff() sync* {
+    yield const K(5);
+  }
+}
+
 @pragma('vm:never-inline')
 Function tearOff(dynamic o) {
   return o.tornOff;
@@ -165,6 +185,7 @@
   }
   print(tearOff(args.isEmpty ? A() : B()));
   print(C.tornOff);
+  print(D.tornOff);
 }
 """
 };
@@ -285,6 +306,10 @@
         // with {body}.
         expect(inputDartSymbolNames['C'], contains('[tear-off] tornOff'));
 
+        // Presence of sync* modifier should not cause tear-off name to end
+        // with {body}.
+        expect(inputDartSymbolNames['D'], contains('[tear-off] tornOff'));
+
         // Check that output does not contain '[unknown stub]'
         expect(symbolRawNames[''][''], isNot(contains('[unknown stub]')),
             reason: 'All stubs must be named');
@@ -306,6 +331,7 @@
         expect(inputLib.children, contains('A'));
         expect(inputLib.children, contains('B'));
         expect(inputLib.children, contains('C'));
+        expect(inputLib.children, contains('D'));
 
         final topLevel = inputLib.children[''];
         expect(topLevel.children, contains('makeSomeClosures'));
@@ -336,6 +362,16 @@
           expect(inputLib.children['C'].children, contains(name));
           expect(inputLib.children['C'].children[name].children, isEmpty);
         }
+
+        for (var name in [
+          'tornOff{body}',
+          'tornOff{body depth 2}',
+          'tornOff',
+          '[tear-off] tornOff'
+        ]) {
+          expect(inputLib.children['D'].children, contains(name));
+          expect(inputLib.children['D'].children[name].children, isEmpty);
+        }
       });
     });
 
@@ -356,6 +392,10 @@
             bySymbol.buckets,
             contains(bySymbol.bucketFor(
                 'package:input', 'package:input/input.dart', 'C', 'tornOff')));
+        expect(
+            bySymbol.buckets,
+            contains(bySymbol.bucketFor(
+                'package:input', 'package:input/input.dart', 'D', 'tornOff')));
 
         final byClass = computeHistogram(info, HistogramType.byClass);
         expect(
@@ -370,6 +410,10 @@
             byClass.buckets,
             contains(byClass.bucketFor('package:input',
                 'package:input/input.dart', 'C', 'does-not-matter')));
+        expect(
+            byClass.buckets,
+            contains(byClass.bucketFor('package:input',
+                'package:input/input.dart', 'D', 'does-not-matter')));
 
         final byLibrary = computeHistogram(info, HistogramType.byLibrary);
         expect(
@@ -538,6 +582,21 @@
             .where((n) => n.type == 'Class')
             .map((n) => n.name);
         expect(classesOwnedByC, equals(['C']));
+
+        final classD = inputLib.children['D'];
+        expect(classD.children, contains('tornOff'));
+        for (var name in ['tornOff{body}', '[tear-off] tornOff']) {
+          expect(classD.children['tornOff'].children, contains(name));
+        }
+        expect(classD.children['tornOff'].children['tornOff{body}'].children,
+            contains('tornOff{body depth 2}'));
+
+        // Verify that [ProgramInfoNode] owns its corresponding snapshot [Node].
+        final classesOwnedByD = info.snapshotInfo.snapshot.nodes
+            .where((n) => info.snapshotInfo.ownerOf(n) == classD)
+            .where((n) => n.type == 'Class')
+            .map((n) => n.name);
+        expect(classesOwnedByD, equals(['D']));
       });
     });
 
@@ -592,6 +651,10 @@
             bySymbol.buckets,
             contains(bySymbol.bucketFor(
                 'package:input', 'package:input/input.dart', 'C', 'tornOff')));
+        expect(
+            bySymbol.buckets,
+            contains(bySymbol.bucketFor(
+                'package:input', 'package:input/input.dart', 'D', 'tornOff')));
 
         final byClass = computeHistogram(info, HistogramType.byClass);
         expect(
@@ -606,6 +669,10 @@
             byClass.buckets,
             contains(byClass.bucketFor('package:input',
                 'package:input/input.dart', 'C', 'does-not-matter')));
+        expect(
+            byClass.buckets,
+            contains(byClass.bucketFor('package:input',
+                'package:input/input.dart', 'D', 'does-not-matter')));
 
         final byLibrary = computeHistogram(info, HistogramType.byLibrary);
         expect(
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index 81908d7..af19ffa 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -911,11 +911,11 @@
       AutoTraceObjectName(func, MakeDisambiguatedFunctionName(s, func));
       WriteFromTo(func);
       if (kind == Snapshot::kFullAOT) {
-        WriteField(func, code());
+        WriteCompressedField(func, code);
       } else if (s->kind() == Snapshot::kFullJIT) {
-        NOT_IN_PRECOMPILED(WriteField(func, unoptimized_code()));
-        WriteField(func, code());
-        WriteField(func, ic_data_array());
+        NOT_IN_PRECOMPILED(WriteCompressedField(func, unoptimized_code));
+        WriteCompressedField(func, code);
+        WriteCompressedField(func, ic_data_array);
       }
 
       if (kind != Snapshot::kFullAOT) {
@@ -1087,11 +1087,11 @@
       ClosureDataPtr data = objects_[i];
       AutoTraceObject(data);
       if (s->kind() != Snapshot::kFullAOT) {
-        WriteField(data, context_scope());
+        WriteCompressedField(data, context_scope);
       }
-      WriteField(data, parent_function());
-      WriteField(data, closure());
-      WriteField(data, default_type_arguments());
+      WriteCompressedField(data, parent_function);
+      WriteCompressedField(data, closure);
+      WriteCompressedField(data, default_type_arguments);
       s->WriteUnsigned(
           static_cast<intptr_t>(data->untag()->default_type_arguments_kind_));
     }
@@ -1268,16 +1268,16 @@
       FieldPtr field = objects_[i];
       AutoTraceObjectName(field, field->untag()->name());
 
-      WriteField(field, name());
-      WriteField(field, owner());
-      WriteField(field, type());
+      WriteCompressedField(field, name);
+      WriteCompressedField(field, owner);
+      WriteCompressedField(field, type);
       // Write out the initializer function and initial value if not in AOT.
-      WriteField(field, initializer_function());
+      WriteCompressedField(field, initializer_function);
       if (kind != Snapshot::kFullAOT) {
-        WriteField(field, guarded_list_length());
+        WriteCompressedField(field, guarded_list_length);
       }
       if (kind == Snapshot::kFullJIT) {
-        WriteField(field, dependent_code());
+        WriteCompressedField(field, dependent_code);
       }
 
       if (kind != Snapshot::kFullAOT) {
@@ -2814,7 +2814,7 @@
       AutoTraceObject(handlers);
       const intptr_t length = handlers->untag()->num_entries_;
       s->WriteUnsigned(length);
-      WriteField(handlers, handled_types_data());
+      WriteCompressedField(handlers, handled_types_data);
       for (intptr_t j = 0; j < length; j++) {
         const ExceptionHandlerInfo& info = handlers->untag()->data()[j];
         s->Write<uint32_t>(info.handler_pc_offset);
@@ -3362,7 +3362,7 @@
     for (intptr_t i = 0; i < count; i++) {
       LoadingUnitPtr unit = objects_[i];
       AutoTraceObject(unit);
-      WriteField(unit, parent());
+      WriteCompressedField(unit, parent);
       s->Write<int32_t>(unit->untag()->id_);
     }
   }
diff --git a/runtime/vm/clustered_snapshot.h b/runtime/vm/clustered_snapshot.h
index 403b206..0888bbb 100644
--- a/runtime/vm/clustered_snapshot.h
+++ b/runtime/vm/clustered_snapshot.h
@@ -502,6 +502,8 @@
 #define PushFromTo(obj, ...) s->PushFromTo(obj, ##__VA_ARGS__);
 
 #define WriteField(obj, field) s->WritePropertyRef(obj->untag()->field, #field)
+#define WriteCompressedField(obj, name)                                        \
+  s->WritePropertyRef(obj->untag()->name(), #name "_")
 
 class SerializerWritingObjectScope {
  public:
diff --git a/runtime/vm/compiler/assembler/disassembler.cc b/runtime/vm/compiler/assembler/disassembler.cc
index 9e19290..e950867 100644
--- a/runtime/vm/compiler/assembler/disassembler.cc
+++ b/runtime/vm/compiler/assembler/disassembler.cc
@@ -463,8 +463,7 @@
   TextBuffer buffer(128);
   const char* function_fullname = function.ToFullyQualifiedCString();
   buffer.Printf("%s", Function::KindToCString(function.kind()));
-  if (function.IsInvokeFieldDispatcher() ||
-      function.IsNoSuchMethodDispatcher()) {
+  if (function.HasSavedArgumentsDescriptor()) {
     const auto& args_desc_array = Array::Handle(function.saved_args_desc());
     const ArgumentsDescriptor args_desc(args_desc_array);
     buffer.AddString(", ");
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index 981a536..b42d9de 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -55,8 +55,7 @@
               Function::KindToCString(function_.kind()));
     // Output saved arguments descriptor information for dispatchers that
     // have it, so it's easy to see which dispatcher this graph represents.
-    if (function_.IsInvokeFieldDispatcher() ||
-        function_.IsNoSuchMethodDispatcher()) {
+    if (function_.HasSavedArgumentsDescriptor()) {
       const auto& args_desc_array = Array::Handle(function_.saved_args_desc());
       const ArgumentsDescriptor args_desc(args_desc_array);
       THR_Print(", %s", args_desc.ToCString());
diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
index e55d218..917e820 100644
--- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h
+++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
@@ -434,8 +434,7 @@
 
   // Returns whether this function has a saved arguments descriptor array.
   bool has_saved_args_desc_array() {
-    return function_.IsInvokeFieldDispatcher() ||
-           function_.IsNoSuchMethodDispatcher();
+    return function_.HasSavedArgumentsDescriptor();
   }
 
   // Returns the saved arguments descriptor array for functions that have them.
diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc
index 14d1b20..f55386b 100644
--- a/runtime/vm/compiler/frontend/scope_builder.cc
+++ b/runtime/vm/compiler/frontend/scope_builder.cc
@@ -81,7 +81,7 @@
     enclosing_scope->set_context_level(0);
     enclosing_scope->AddVariable(receiver_variable);
     enclosing_scope->AddContextVariable(receiver_variable);
-  } else if (function.IsLocalFunction()) {
+  } else if (function.HasParent()) {
     enclosing_scope = LocalScope::RestoreOuterScope(
         ContextScope::Handle(Z, function.context_scope()));
   }
diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc
index ec04ca2..058d039 100644
--- a/runtime/vm/dart_entry.cc
+++ b/runtime/vm/dart_entry.cc
@@ -412,24 +412,28 @@
   return names.ptr();
 }
 
-void ArgumentsDescriptor::PrintTo(BaseTextBuffer* buffer) const {
-  buffer->Printf("%" Pd " arg%s", Count(), Count() == 1 ? "" : "s");
+void ArgumentsDescriptor::PrintTo(BaseTextBuffer* buffer,
+                                  bool show_named_positions) const {
   if (TypeArgsLen() > 0) {
-    buffer->Printf(", %" Pd " type arg%s", TypeArgsLen(),
-                   TypeArgsLen() == 1 ? "" : "s");
+    buffer->Printf("<%" Pd ">", TypeArgsLen());
   }
+  buffer->Printf("(%" Pd "", Count());
   if (NamedCount() > 0) {
-    buffer->AddString(", names [");
+    buffer->AddString(" {");
     auto& str = String::Handle();
     for (intptr_t i = 0; i < NamedCount(); i++) {
       if (i != 0) {
         buffer->AddString(", ");
       }
       str = NameAt(i);
-      buffer->Printf("'%s' (%" Pd ")", str.ToCString(), PositionAt(i));
+      buffer->Printf("%s", str.ToCString());
+      if (show_named_positions) {
+        buffer->Printf(" (%" Pd ")", PositionAt(i));
+      }
     }
-    buffer->Printf("]");
+    buffer->Printf("}");
   }
+  buffer->Printf(")");
 }
 
 const char* ArgumentsDescriptor::ToCString() const {
diff --git a/runtime/vm/dart_entry.h b/runtime/vm/dart_entry.h
index 4b5fd32..2a33464 100644
--- a/runtime/vm/dart_entry.h
+++ b/runtime/vm/dart_entry.h
@@ -46,7 +46,7 @@
   bool MatchesNameAt(intptr_t i, const String& other) const;
   // Returns array of argument names in the arguments order.
   ArrayPtr GetArgumentNames() const;
-  void PrintTo(BaseTextBuffer* buffer) const;
+  void PrintTo(BaseTextBuffer* buffer, bool show_named_positions = false) const;
   const char* ToCString() const;
 
   // Generated code support.
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index a0af589..8d31193 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -7382,11 +7382,11 @@
 }
 
 bool Function::IsInFactoryScope() const {
-  if (!IsLocalFunction()) {
+  if (!HasParent()) {
     return IsFactory();
   }
   Function& outer_function = Function::Handle(parent_function());
-  while (outer_function.IsLocalFunction()) {
+  while (outer_function.HasParent()) {
     outer_function = outer_function.parent_function();
   }
   return outer_function.IsFactory();
@@ -9408,16 +9408,31 @@
   return printer.buffer();
 }
 
-void Function::PrintName(const NameFormattingParams& params,
-                         BaseTextBuffer* printer) const {
-  // If |this| is the generated asynchronous body closure, use the
-  // name of the parent function.
-  Function& fun = Function::Handle(ptr());
-
+static void FunctionPrintNameHelper(const Function& fun,
+                                    const NameFormattingParams& params,
+                                    BaseTextBuffer* printer) {
+  if (fun.IsLocalFunction()) {
+    if (params.include_parent_name) {
+      const auto& parent = Function::Handle(fun.parent_function());
+      parent.PrintName(params, printer);
+      // A function's scrubbed name and its user visible name are identical.
+      printer->AddString(".");
+    }
+    if (params.disambiguate_names &&
+        fun.name() == Symbols::AnonymousClosure().ptr()) {
+      printer->Printf("<anonymous closure @%" Pd ">", fun.token_pos().Pos());
+    } else {
+      printer->AddString(fun.NameCString(params.name_visibility));
+    }
+    return;
+  }
   if (params.disambiguate_names) {
     if (fun.IsInvokeFieldDispatcher()) {
       printer->AddString("[invoke-field] ");
     }
+    if (fun.IsNoSuchMethodDispatcher()) {
+      printer->AddString("[no-such-method] ");
+    }
     if (fun.IsImplicitClosureFunction()) {
       printer->AddString("[tear-off] ");
     }
@@ -9426,53 +9441,13 @@
     }
   }
 
-  if (fun.IsNonImplicitClosureFunction()) {
-    // Sniff the parent function.
-    fun = fun.parent_function();
-    ASSERT(!fun.IsNull());
-    if (!fun.IsAsyncGenerator() && !fun.IsAsyncFunction() &&
-        !fun.IsSyncGenerator()) {
-      // Parent function is not the generator of an asynchronous body closure,
-      // start at |this|.
-      fun = ptr();
-    }
-  }
-  if (IsClosureFunction()) {
-    if (fun.IsLocalFunction() && !fun.IsImplicitClosureFunction()) {
-      Function& parent = Function::Handle(fun.parent_function());
-      if (parent.IsAsyncClosure() || parent.IsSyncGenClosureMaker() ||
-          parent.IsAsyncGenClosure()) {
-        // Skip the closure and use the real function name found in
-        // the parent.
-        parent = parent.parent_function();
-      }
-      if (params.include_parent_name) {
-        parent.PrintName(params, printer);
-        // A function's scrubbed name and its user visible name are identical.
-        printer->AddString(".");
-      }
-      if (params.disambiguate_names &&
-          fun.name() == Symbols::AnonymousClosure().ptr()) {
-        printer->Printf("<anonymous closure @%" Pd ">", fun.token_pos().Pos());
-      } else {
-        printer->AddString(fun.NameCString(params.name_visibility));
-      }
-      // If we skipped rewritten async/async*/sync* body then append a suffix
-      // to the end of the name.
-      if (fun.ptr() != ptr() && params.disambiguate_names) {
-        printer->AddString("{body}");
-      }
-      return;
-    }
-  }
-
   if (fun.kind() == UntaggedFunction::kConstructor) {
     printer->AddString("new ");
   } else if (params.include_class_name) {
-    const Class& cls = Class::Handle(Owner());
+    const Class& cls = Class::Handle(fun.Owner());
     if (!cls.IsTopLevel()) {
       const Class& mixin = Class::Handle(cls.Mixin());
-      printer->AddString(params.name_visibility == kUserVisibleName
+      printer->AddString(params.name_visibility == Object::kUserVisibleName
                              ? mixin.UserVisibleNameCString()
                              : cls.NameCString(params.name_visibility));
       printer->AddString(".");
@@ -9481,35 +9456,37 @@
 
   printer->AddString(fun.NameCString(params.name_visibility));
 
-  // If we skipped rewritten async/async*/sync* body then append a suffix
-  // to the end of the name.
-  if (fun.ptr() != ptr() && params.disambiguate_names) {
-    printer->AddString("{body}");
+  // Dispatchers that are created with an arguments descriptor need both the
+  // name and the saved arguments descriptor to disambiguate.
+  if (params.disambiguate_names && fun.HasSavedArgumentsDescriptor()) {
+    const auto& args_desc_array = Array::Handle(fun.saved_args_desc());
+    const ArgumentsDescriptor args_desc(args_desc_array);
+    args_desc.PrintTo(printer);
   }
+}
 
-  // Field dispatchers are specialized for an argument descriptor so there
-  // might be multiples of them with the same name but different argument
-  // descriptors. Add a suffix to disambiguate.
-  if (params.disambiguate_names && fun.IsInvokeFieldDispatcher()) {
-    printer->AddString(" ");
-    if (NumTypeParameters() != 0) {
-      printer->Printf("<%" Pd ">", fun.NumTypeParameters());
+void Function::PrintName(const NameFormattingParams& params,
+                         BaseTextBuffer* printer) const {
+  if (!IsLocalFunction()) {
+    FunctionPrintNameHelper(*this, params, printer);
+    return;
+  }
+  auto& fun = Function::Handle(ptr());
+  intptr_t fun_depth = 0;
+  // If |this| is a generated body closure, start with the closest
+  // non-generated parent function.
+  while (fun.is_generated_body()) {
+    fun = fun.parent_function();
+    fun_depth++;
+  }
+  FunctionPrintNameHelper(fun, params, printer);
+  // If we skipped generated bodies then append a suffix to the end.
+  if (fun_depth > 0 && params.disambiguate_names) {
+    printer->AddString("{body");
+    if (fun_depth > 1) {
+      printer->Printf(" depth %" Pd "", fun_depth);
     }
-    printer->AddString("(");
-    printer->Printf("%" Pd "", fun.num_fixed_parameters());
-    if (fun.NumOptionalPositionalParameters() != 0) {
-      printer->Printf(" [%" Pd "]", fun.NumOptionalPositionalParameters());
-    }
-    if (fun.HasOptionalNamedParameters()) {
-      printer->AddString(" {");
-      String& name = String::Handle();
-      for (intptr_t i = 0; i < fun.NumOptionalNamedParameters(); i++) {
-        name = fun.ParameterNameAt(fun.num_fixed_parameters() + i);
-        printer->Printf("%s%s", i > 0 ? ", " : "", name.ToCString());
-      }
-      printer->AddString("}");
-    }
-    printer->AddString(")");
+    printer->AddString("}");
   }
 }
 
@@ -9800,7 +9777,7 @@
 bool Function::PrologueNeedsArgumentsDescriptor() const {
   // These functions have a saved compile-time arguments descriptor that is
   // used in lieu of the runtime arguments descriptor in generated IL.
-  if (IsInvokeFieldDispatcher() || IsNoSuchMethodDispatcher()) {
+  if (HasSavedArgumentsDescriptor()) {
     return false;
   }
   // The prologue of those functions need to examine the arg descriptor for
@@ -9890,7 +9867,7 @@
     default:
       UNREACHABLE();
   }
-  if (IsNoSuchMethodDispatcher() || IsInvokeFieldDispatcher()) {
+  if (HasSavedArgumentsDescriptor()) {
     const auto& args_desc_array = Array::Handle(zone, saved_args_desc());
     const ArgumentsDescriptor args_desc(args_desc_array);
     buffer.AddChar('[');
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 0708f3d..0512afe 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2724,6 +2724,10 @@
   void set_saved_args_desc(const Array& array) const;
   ArrayPtr saved_args_desc() const;
 
+  bool HasSavedArgumentsDescriptor() const {
+    return IsInvokeFieldDispatcher() || IsNoSuchMethodDispatcher();
+  }
+
   void set_accessor_field(const Field& value) const;
   FieldPtr accessor_field() const;
 
@@ -3328,8 +3332,13 @@
     return IsImplicitClosureFunction() && !is_static();
   }
 
-  // Returns true if this function represents a local function.
-  bool IsLocalFunction() const { return parent_function() != Function::null(); }
+  // Returns true if this function has a parent function.
+  bool HasParent() const { return parent_function() != Function::null(); }
+
+  // Returns true if this function is a local function.
+  bool IsLocalFunction() const {
+    return !IsImplicitClosureFunction() && HasParent();
+  }
 
   // Returns true if this function represents an ffi trampoline.
   bool IsFfiTrampoline() const {
@@ -3438,7 +3447,7 @@
   //      }
   //   }
   bool IsSyncGenClosure() const {
-    return (parent_function() != Function::null()) &&
+    return is_generated_body() &&
            Function::Handle(parent_function()).IsSyncGenClosureMaker();
   }
 
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index e3b428e..604900f 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -616,8 +616,7 @@
   }
   const Function& function =
       Function::Handle(caller_frame->LookupDartFunction());
-  if (function.IsInvokeFieldDispatcher() ||
-      function.IsNoSuchMethodDispatcher()) {
+  if (function.HasSavedArgumentsDescriptor()) {
     const auto& args_desc_array = Array::Handle(function.saved_args_desc());
     const ArgumentsDescriptor args_desc(args_desc_array);
     OS::PrintErr(" -> Function %s [%s]\n", function.ToFullyQualifiedCString(),
diff --git a/runtime/vm/scopes.cc b/runtime/vm/scopes.cc
index 3b08708..c643b46 100644
--- a/runtime/vm/scopes.cc
+++ b/runtime/vm/scopes.cc
@@ -362,7 +362,7 @@
   const ContextScope& context_scope =
       ContextScope::Handle(func.context_scope());
   if (!context_scope.IsNull()) {
-    ASSERT(func.IsLocalFunction());
+    ASSERT(func.HasParent());
     for (int i = 0; i < context_scope.num_variables(); i++) {
       String& name = String::Handle(context_scope.NameAt(i));
       UntaggedLocalVarDescriptors::VarInfoKind kind;
diff --git a/runtime/vm/stack_trace.cc b/runtime/vm/stack_trace.cc
index f200269..74fa21a 100644
--- a/runtime/vm/stack_trace.cc
+++ b/runtime/vm/stack_trace.cc
@@ -238,7 +238,7 @@
     return GetCallerInFutureImpl(future_);
   }
 
-  if (receiver_function_.IsLocalFunction()) {
+  if (receiver_function_.HasParent()) {
     parent_function_ = receiver_function_.parent_function();
     if (parent_function_.recognized_kind() ==
         MethodRecognizer::kFutureTimeout) {