[vm/compiler] Teach compiler to fold load of type_args_len from constant args desc.

This allows optimizer to eliminate redundant code branching on type_args_len
which arises when inlining generic methods.

Start refactoring how loads of native fields are represented: instead of simply
setting a bunch of unrelated attributes introduce NativeFieldDesc structure
that captures attributes of native fields, which don't have representation
as Field objects.

Over the time we should be able to eliminate "raw" LoadField/StoreInstanceField
instructions that just address fields by their offset and use NativeFieldDesc-s
in load forwarding to disambiguate different native fields that happen to
have the same offset.

Change-Id: I91658888e17bd68c5f9d4f2e1a783b2827efe61f
Reviewed-on: https://dart-review.googlesource.com/62304
Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc
index ab5b854..a9fde3d 100644
--- a/runtime/vm/compiler/backend/constant_propagator.cc
+++ b/runtime/vm/compiler/backend/constant_propagator.cc
@@ -775,7 +775,8 @@
 
 void ConstantPropagator::VisitLoadField(LoadFieldInstr* instr) {
   Value* instance = instr->instance();
-  if ((instr->recognized_kind() == MethodRecognizer::kObjectArrayLength) &&
+  if ((instr->native_field() != nullptr) &&
+      (instr->native_field()->kind() == NativeFieldDesc::kArray_length) &&
       instance->definition()->OriginalDefinition()->IsCreateArray()) {
     Value* num_elements = instance->definition()
                               ->OriginalDefinition()
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 81e98f8..fd9b374 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -562,6 +562,73 @@
   return mask;
 }
 
+const NativeFieldDesc* NativeFieldDesc::Get(Kind kind) {
+  static const NativeFieldDesc fields[] = {
+#define IMMUTABLE true
+#define MUTABLE false
+#define DEFINE_NATIVE_FIELD(ClassName, FieldName, cid, mutability)             \
+  NativeFieldDesc(k##ClassName##_##FieldName, ClassName::FieldName##_offset(), \
+                  k##cid##Cid, mutability),
+
+      NATIVE_FIELDS_LIST(DEFINE_NATIVE_FIELD)
+
+#undef DEFINE_FIELD
+#undef MUTABLE
+#undef IMMUTABLE
+  };
+
+  return &fields[kind];
+}
+
+const NativeFieldDesc* NativeFieldDesc::GetLengthFieldForArrayCid(
+    intptr_t array_cid) {
+  if (RawObject::IsExternalTypedDataClassId(array_cid) ||
+      RawObject::IsTypedDataClassId(array_cid)) {
+    return Get(kTypedData_length);
+  }
+
+  switch (array_cid) {
+    case kGrowableObjectArrayCid:
+      return Get(kGrowableObjectArray_length);
+
+    case kOneByteStringCid:
+    case kTwoByteStringCid:
+    case kExternalOneByteStringCid:
+    case kExternalTwoByteStringCid:
+      return Get(kString_length);
+
+    case kArrayCid:
+    case kImmutableArrayCid:
+      return Get(kArray_length);
+
+    default:
+      UNREACHABLE();
+      return nullptr;
+  }
+}
+
+RawAbstractType* NativeFieldDesc::type() const {
+  if (cid() == kSmiCid) {
+    return Type::SmiType();
+  }
+
+  return Type::DynamicType();
+}
+
+const char* NativeFieldDesc::name() const {
+  switch (kind()) {
+#define HANDLE_CASE(ClassName, FieldName, cid, mutability)                     \
+  case k##ClassName##_##FieldName:                                             \
+    return #ClassName "." #FieldName;
+
+    NATIVE_FIELDS_LIST(HANDLE_CASE)
+
+#undef HANDLE_CASE
+  }
+  UNREACHABLE();
+  return nullptr;
+}
+
 bool LoadFieldInstr::IsUnboxedLoad() const {
   return FLAG_unbox_numeric_fields && (field() != NULL) &&
          FlowGraphCompiler::IsUnboxedField(*field());
@@ -2240,34 +2307,26 @@
 }
 
 bool LoadFieldInstr::IsImmutableLengthLoad() const {
-  switch (recognized_kind()) {
-    case MethodRecognizer::kObjectArrayLength:
-    case MethodRecognizer::kImmutableArrayLength:
-    case MethodRecognizer::kTypedDataLength:
-    case MethodRecognizer::kStringBaseLength:
-      return true;
-    default:
-      return false;
-  }
-}
+  if (native_field() != nullptr) {
+    switch (native_field()->kind()) {
+      case NativeFieldDesc::kArray_length:
+      case NativeFieldDesc::kTypedData_length:
+      case NativeFieldDesc::kString_length:
+        return true;
+      case NativeFieldDesc::kGrowableObjectArray_length:
+        return false;
 
-MethodRecognizer::Kind LoadFieldInstr::RecognizedKindFromArrayCid(
-    intptr_t cid) {
-  if (RawObject::IsTypedDataClassId(cid) ||
-      RawObject::IsExternalTypedDataClassId(cid)) {
-    return MethodRecognizer::kTypedDataLength;
+      // Not length loads.
+      case NativeFieldDesc::kLinkedHashMap_index:
+      case NativeFieldDesc::kLinkedHashMap_data:
+      case NativeFieldDesc::kLinkedHashMap_hash_mask:
+      case NativeFieldDesc::kLinkedHashMap_used_data:
+      case NativeFieldDesc::kLinkedHashMap_deleted_keys:
+      case NativeFieldDesc::kArgumentsDescriptor_type_args_len:
+        return false;
+    }
   }
-  switch (cid) {
-    case kArrayCid:
-      return MethodRecognizer::kObjectArrayLength;
-    case kImmutableArrayCid:
-      return MethodRecognizer::kImmutableArrayLength;
-    case kGrowableObjectArrayCid:
-      return MethodRecognizer::kGrowableArrayLength;
-    default:
-      UNREACHABLE();
-      return MethodRecognizer::kUnknown;
-  }
+  return false;
 }
 
 bool LoadFieldInstr::IsFixedLengthArrayCid(intptr_t cid) {
@@ -2297,7 +2356,22 @@
 }
 
 bool LoadFieldInstr::Evaluate(const Object& instance, Object* result) {
-  if (field() == NULL || !field()->is_final() || !instance.IsInstance()) {
+  if (native_field() != nullptr) {
+    switch (native_field()->kind()) {
+      case NativeFieldDesc::kArgumentsDescriptor_type_args_len:
+        if (instance.IsArray() && Array::Cast(instance).IsImmutable()) {
+          ArgumentsDescriptor desc(Array::Cast(instance));
+          *result = Smi::New(desc.TypeArgsLen());
+          return true;
+        }
+        return false;
+
+      default:
+        break;
+    }
+  }
+
+  if (field() == nullptr || !field()->is_final() || !instance.IsInstance()) {
     return false;
   }
 
@@ -2321,30 +2395,22 @@
   if (!HasUses()) return NULL;
 
   if (IsImmutableLengthLoad()) {
-    // For fixed length arrays if the array is the result of a known constructor
-    // call we can replace the length load with the length argument passed to
-    // the constructor.
-    StaticCallInstr* call =
-        instance()->definition()->OriginalDefinition()->AsStaticCall();
-    if (call != NULL) {
+    Definition* array = instance()->definition()->OriginalDefinition();
+    if (StaticCallInstr* call = array->AsStaticCall()) {
+      // For fixed length arrays if the array is the result of a known
+      // constructor call we can replace the length load with the length
+      // argument passed to the constructor.
       if (call->is_known_list_constructor() &&
           IsFixedLengthArrayCid(call->Type()->ToCid())) {
         return call->ArgumentAt(1);
       }
-    }
-
-    CreateArrayInstr* create_array =
-        instance()->definition()->OriginalDefinition()->AsCreateArray();
-    if ((create_array != NULL) &&
-        (recognized_kind() == MethodRecognizer::kObjectArrayLength)) {
-      return create_array->num_elements()->definition();
-    }
-
-    // For arrays with guarded lengths, replace the length load
-    // with a constant.
-    LoadFieldInstr* load_array =
-        instance()->definition()->OriginalDefinition()->AsLoadField();
-    if (load_array != NULL) {
+    } else if (CreateArrayInstr* create_array = array->AsCreateArray()) {
+      if (native_field() == NativeFieldDesc::Array_length()) {
+        return create_array->num_elements()->definition();
+      }
+    } else if (LoadFieldInstr* load_array = array->AsLoadField()) {
+      // For arrays with guarded lengths, replace the length load
+      // with a constant.
       const Field* field = load_array->field();
       if ((field != NULL) && (field->guarded_list_length() >= 0)) {
         return flow_graph->GetConstant(
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 3c2a601..ae7d1fa 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -4904,6 +4904,67 @@
   DISALLOW_COPY_AND_ASSIGN(LoadClassIdInstr);
 };
 
+#define NATIVE_FIELDS_LIST(V)                                                  \
+  V(Array, length, Smi, IMMUTABLE)                                             \
+  V(GrowableObjectArray, length, Smi, MUTABLE)                                 \
+  V(TypedData, length, Smi, IMMUTABLE)                                         \
+  V(String, length, Smi, IMMUTABLE)                                            \
+  V(LinkedHashMap, index, TypedDataUint32Array, MUTABLE)                       \
+  V(LinkedHashMap, data, Array, MUTABLE)                                       \
+  V(LinkedHashMap, hash_mask, Smi, MUTABLE)                                    \
+  V(LinkedHashMap, used_data, Smi, MUTABLE)                                    \
+  V(LinkedHashMap, deleted_keys, Smi, MUTABLE)                                 \
+  V(ArgumentsDescriptor, type_args_len, Smi, IMMUTABLE)
+
+class NativeFieldDesc {
+ public:
+  enum Kind {
+#define DECLARE_KIND(ClassName, FieldName, cid, mutability)                    \
+  k##ClassName##_##FieldName,
+    NATIVE_FIELDS_LIST(DECLARE_KIND)
+#undef DECLARE_KIND
+  };
+
+#define DEFINE_GETTER(ClassName, FieldName, cid, mutability)                   \
+  static const NativeFieldDesc* ClassName##_##FieldName() {                    \
+    return Get(k##ClassName##_##FieldName);                                    \
+  }
+
+  NATIVE_FIELDS_LIST(DEFINE_GETTER)
+#undef DEFINE_GETTER
+
+  static const NativeFieldDesc* Get(Kind kind);
+  static const NativeFieldDesc* GetLengthFieldForArrayCid(intptr_t array_cid);
+
+  const char* name() const;
+
+  Kind kind() const { return kind_; }
+
+  intptr_t offset_in_bytes() const { return offset_in_bytes_; }
+
+  bool is_immutable() const { return immutable_; }
+
+  intptr_t cid() const { return cid_; }
+
+  RawAbstractType* type() const;
+
+ private:
+  NativeFieldDesc(Kind kind,
+                  intptr_t offset_in_bytes,
+                  intptr_t cid,
+                  bool immutable)
+      : kind_(kind),
+        offset_in_bytes_(offset_in_bytes),
+        immutable_(immutable),
+        cid_(cid) {}
+
+  const Kind kind_;
+  const intptr_t offset_in_bytes_;
+  const bool immutable_;
+
+  const intptr_t cid_;
+};
+
 class LoadFieldInstr : public TemplateDefinition<1, NoThrow> {
  public:
   LoadFieldInstr(Value* instance,
@@ -4914,12 +4975,28 @@
         type_(type),
         result_cid_(kDynamicCid),
         immutable_(false),
-        recognized_kind_(MethodRecognizer::kUnknown),
-        field_(NULL),
+        native_field_(nullptr),
+        field_(nullptr),
         token_pos_(token_pos) {
     ASSERT(offset_in_bytes >= 0);
     // May be null if field is not an instance.
-    ASSERT(type.IsZoneHandle() || type.IsReadOnlyHandle());
+    ASSERT(type_.IsZoneHandle() || type_.IsReadOnlyHandle());
+    SetInputAt(0, instance);
+  }
+
+  LoadFieldInstr(Value* instance,
+                 const NativeFieldDesc* native_field,
+                 TokenPosition token_pos)
+      : offset_in_bytes_(native_field->offset_in_bytes()),
+        type_(AbstractType::ZoneHandle(native_field->type())),
+        result_cid_(native_field->cid()),
+        immutable_(native_field->is_immutable()),
+        native_field_(native_field),
+        field_(nullptr),
+        token_pos_(token_pos) {
+    ASSERT(offset_in_bytes_ >= 0);
+    // May be null if field is not an instance.
+    ASSERT(type_.IsZoneHandle() || type_.IsReadOnlyHandle());
     SetInputAt(0, instance);
   }
 
@@ -4932,7 +5009,7 @@
         type_(type),
         result_cid_(kDynamicCid),
         immutable_(false),
-        recognized_kind_(MethodRecognizer::kUnknown),
+        native_field_(nullptr),
         field_(field),
         token_pos_(token_pos) {
     ASSERT(field->IsZoneHandle());
@@ -4940,7 +5017,7 @@
     ASSERT(type.IsZoneHandle() || type.IsReadOnlyHandle());
     SetInputAt(0, instance);
 
-    if (parsed_function != NULL && field->guarded_cid() != kIllegalCid) {
+    if (parsed_function != nullptr && field->guarded_cid() != kIllegalCid) {
       if (!field->is_nullable() || (field->guarded_cid() == kNullCid)) {
         set_result_cid(field->guarded_cid());
       }
@@ -4965,11 +5042,7 @@
 
   bool IsPotentialUnboxedLoad() const;
 
-  void set_recognized_kind(MethodRecognizer::Kind kind) {
-    recognized_kind_ = kind;
-  }
-
-  MethodRecognizer::Kind recognized_kind() const { return recognized_kind_; }
+  const NativeFieldDesc* native_field() const { return native_field_; }
 
   DECLARE_INSTRUCTION(LoadField)
   virtual CompileType ComputeType() const;
@@ -4989,8 +5062,6 @@
 
   virtual Definition* Canonicalize(FlowGraph* flow_graph);
 
-  static MethodRecognizer::Kind RecognizedKindFromArrayCid(intptr_t cid);
-
   static bool IsFixedLengthArrayCid(intptr_t cid);
 
   virtual bool AllowsCSE() const { return immutable_; }
@@ -5006,7 +5077,7 @@
   intptr_t result_cid_;
   bool immutable_;
 
-  MethodRecognizer::Kind recognized_kind_;
+  const NativeFieldDesc* native_field_;
   const Field* field_;
   const TokenPosition token_pos_;
 
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index 44e1b37..2493b68 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -649,7 +649,7 @@
   instance()->PrintTo(f);
   f->Print(", %" Pd, offset_in_bytes());
 
-  if (field() != NULL) {
+  if (field() != nullptr) {
     f->Print(" {%s}", String::Handle(field()->name()).ToCString());
     const char* expected = "?";
     if (field()->guarded_cid() != kIllegalCid) {
@@ -662,7 +662,13 @@
              expected);
   }
 
-  f->Print(", immutable=%d", immutable_);
+  if (native_field() != nullptr) {
+    f->Print(" {%s}", native_field()->name());
+  }
+
+  if (immutable_) {
+    f->Print(", immutable");
+  }
 }
 
 void InstantiateTypeInstr::PrintOperandsTo(BufferFormatter* f) const {
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index 4ef1a33..682e38d 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -2279,13 +2279,8 @@
                                        bool can_speculate) {
   // Insert array length load and bounds check.
   LoadFieldInstr* length = new (Z) LoadFieldInstr(
-      new (Z) Value(*array), CheckArrayBoundInstr::LengthOffsetFor(array_cid),
-      Type::ZoneHandle(Z, Type::SmiType()), call->token_pos());
-  length->set_is_immutable(
-      CheckArrayBoundInstr::IsFixedLengthArrayType(array_cid));
-  length->set_result_cid(kSmiCid);
-  length->set_recognized_kind(
-      LoadFieldInstr::RecognizedKindFromArrayCid(array_cid));
+      new (Z) Value(*array),
+      NativeFieldDesc::GetLengthFieldForArrayCid(array_cid), call->token_pos());
   *cursor = flow_graph->AppendTo(*cursor, length, NULL, FlowGraph::kValue);
 
   Instruction* bounds_check = NULL;
@@ -2614,12 +2609,8 @@
   ASSERT(array_cid != kDynamicCid);
 
   LoadFieldInstr* length = new (Z) LoadFieldInstr(
-      new (Z) Value(array), CheckArrayBoundInstr::LengthOffsetFor(array_cid),
-      Type::ZoneHandle(Z, Type::SmiType()), call->token_pos());
-  length->set_is_immutable(true);
-  length->set_result_cid(kSmiCid);
-  length->set_recognized_kind(
-      LoadFieldInstr::RecognizedKindFromArrayCid(array_cid));
+      new (Z) Value(array),
+      NativeFieldDesc::GetLengthFieldForArrayCid(array_cid), call->token_pos());
   *cursor = flow_graph->AppendTo(*cursor, length, NULL, FlowGraph::kValue);
 
   intptr_t element_size = Instance::ElementSizeFor(array_cid);
@@ -2982,14 +2973,11 @@
                                               Definition* str,
                                               Definition* index,
                                               Instruction* cursor) {
-  LoadFieldInstr* length = new (Z)
-      LoadFieldInstr(new (Z) Value(str), String::length_offset(),
-                     Type::ZoneHandle(Z, Type::SmiType()), str->token_pos());
-  length->set_result_cid(kSmiCid);
-  length->set_is_immutable(true);
-  length->set_recognized_kind(MethodRecognizer::kStringBaseLength);
-
+  LoadFieldInstr* length = new (Z) LoadFieldInstr(
+      new (Z) Value(str), NativeFieldDesc::GetLengthFieldForArrayCid(cid),
+      str->token_pos());
   cursor = flow_graph->AppendTo(cursor, length, NULL, FlowGraph::kValue);
+
   // Bounds check.
   cursor = flow_graph->AppendTo(
       cursor,
diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc
index fd08f03..8c19726 100644
--- a/runtime/vm/compiler/backend/range_analysis.cc
+++ b/runtime/vm/compiler/backend/range_analysis.cc
@@ -2683,25 +2683,42 @@
 }
 
 void LoadFieldInstr::InferRange(RangeAnalysis* analysis, Range* range) {
-  switch (recognized_kind()) {
-    case MethodRecognizer::kObjectArrayLength:
-    case MethodRecognizer::kImmutableArrayLength:
-      *range = Range(RangeBoundary::FromConstant(0),
-                     RangeBoundary::FromConstant(Array::kMaxElements));
-      break;
+  if (native_field() != nullptr) {
+    switch (native_field()->kind()) {
+      case NativeFieldDesc::kArray_length:
+      case NativeFieldDesc::kGrowableObjectArray_length:
+        *range = Range(RangeBoundary::FromConstant(0),
+                       RangeBoundary::FromConstant(Array::kMaxElements));
+        break;
 
-    case MethodRecognizer::kTypedDataLength:
-      *range = Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi());
-      break;
+      case NativeFieldDesc::kTypedData_length:
+        *range = Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi());
+        break;
 
-    case MethodRecognizer::kStringBaseLength:
-      *range = Range(RangeBoundary::FromConstant(0),
-                     RangeBoundary::FromConstant(String::kMaxElements));
-      break;
+      case NativeFieldDesc::kString_length:
+        *range = Range(RangeBoundary::FromConstant(0),
+                       RangeBoundary::FromConstant(String::kMaxElements));
+        break;
 
-    default:
-      Definition::InferRange(analysis, range);
+      case NativeFieldDesc::kLinkedHashMap_index:
+      case NativeFieldDesc::kLinkedHashMap_data:
+        // Not an integer valued field.
+        UNREACHABLE();
+        break;
+
+      case NativeFieldDesc::kLinkedHashMap_hash_mask:
+      case NativeFieldDesc::kLinkedHashMap_used_data:
+      case NativeFieldDesc::kLinkedHashMap_deleted_keys:
+        *range = Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi());
+        break;
+
+      case NativeFieldDesc::kArgumentsDescriptor_type_args_len:
+        *range = Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi());
+        break;
+    }
+    return;
   }
+  Definition::InferRange(analysis, range);
 }
 
 void LoadIndexedInstr::InferRange(RangeAnalysis* analysis, Range* range) {
diff --git a/runtime/vm/compiler/frontend/flow_graph_builder.cc b/runtime/vm/compiler/frontend/flow_graph_builder.cc
index 6ab263e..4d93fdf 100644
--- a/runtime/vm/compiler/frontend/flow_graph_builder.cc
+++ b/runtime/vm/compiler/frontend/flow_graph_builder.cc
@@ -3126,18 +3126,22 @@
   BuildStaticSetter(node, true);  // Result needed.
 }
 
-static intptr_t OffsetForLengthGetter(MethodRecognizer::Kind kind) {
+static const NativeFieldDesc* NativeFieldForLengthGetter(
+    MethodRecognizer::Kind kind) {
   switch (kind) {
     case MethodRecognizer::kObjectArrayLength:
     case MethodRecognizer::kImmutableArrayLength:
-      return Array::length_offset();
+      return NativeFieldDesc::Array_length();
+
     case MethodRecognizer::kTypedDataLength:
       // .length is defined in _TypedList which is the base class for internal
       // and external typed data.
       ASSERT(TypedData::length_offset() == ExternalTypedData::length_offset());
-      return TypedData::length_offset();
+      return NativeFieldDesc::TypedData_length();
+
     case MethodRecognizer::kGrowableArrayLength:
-      return GrowableObjectArray::length_offset();
+      return NativeFieldDesc::GrowableObjectArray_length();
+
     default:
       UNREACHABLE();
       return 0;
@@ -3153,15 +3157,10 @@
 
 LoadFieldInstr* EffectGraphVisitor::BuildNativeGetter(
     NativeBodyNode* node,
-    MethodRecognizer::Kind kind,
-    intptr_t offset,
-    const Type& type,
-    intptr_t class_id) {
+    const NativeFieldDesc* native_field) {
   Value* receiver = Bind(BuildLoadThisVar(node->scope(), node->token_pos()));
   LoadFieldInstr* load =
-      new (Z) LoadFieldInstr(receiver, offset, type, node->token_pos());
-  load->set_result_cid(class_id);
-  load->set_recognized_kind(kind);
+      new (Z) LoadFieldInstr(receiver, native_field, node->token_pos());
   return load;
 }
 
@@ -3200,10 +3199,8 @@
       }
       case MethodRecognizer::kStringBaseLength:
       case MethodRecognizer::kStringBaseIsEmpty: {
-        LoadFieldInstr* load = BuildNativeGetter(
-            node, MethodRecognizer::kStringBaseLength, String::length_offset(),
-            Type::ZoneHandle(Z, Type::SmiType()), kSmiCid);
-        load->set_is_immutable(true);
+        LoadFieldInstr* load =
+            BuildNativeGetter(node, NativeFieldDesc::String_length());
         if (kind == MethodRecognizer::kStringBaseLength) {
           return ReturnDefinition(load);
         }
@@ -3221,9 +3218,7 @@
       case MethodRecognizer::kImmutableArrayLength:
       case MethodRecognizer::kTypedDataLength: {
         LoadFieldInstr* load =
-            BuildNativeGetter(node, kind, OffsetForLengthGetter(kind),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid);
-        load->set_is_immutable(kind != MethodRecognizer::kGrowableArrayLength);
+            BuildNativeGetter(node, NativeFieldForLengthGetter(kind));
         return ReturnDefinition(load);
       }
       case MethodRecognizer::kClassIDgetID: {
@@ -3241,10 +3236,7 @@
         data_load->set_result_cid(kArrayCid);
         Value* data = Bind(data_load);
         LoadFieldInstr* length_load = new (Z) LoadFieldInstr(
-            data, Array::length_offset(), Type::ZoneHandle(Z, Type::SmiType()),
-            node->token_pos());
-        length_load->set_result_cid(kSmiCid);
-        length_load->set_recognized_kind(MethodRecognizer::kObjectArrayLength);
+            data, NativeFieldDesc::Array_length(), node->token_pos());
         return ReturnDefinition(length_load);
       }
       case MethodRecognizer::kListFactory: {
@@ -3342,9 +3334,8 @@
         return ReturnDefinition(create_array);
       }
       case MethodRecognizer::kLinkedHashMap_getIndex: {
-        return ReturnDefinition(BuildNativeGetter(
-            node, kind, LinkedHashMap::index_offset(), Object::dynamic_type(),
-            kTypedDataUint32ArrayCid));
+        return ReturnDefinition(
+            BuildNativeGetter(node, NativeFieldDesc::LinkedHashMap_index()));
       }
       case MethodRecognizer::kLinkedHashMap_setIndex: {
         return ReturnDefinition(DoNativeSetterStoreValue(
@@ -3352,17 +3343,15 @@
       }
       case MethodRecognizer::kLinkedHashMap_getData: {
         return ReturnDefinition(
-            BuildNativeGetter(node, kind, LinkedHashMap::data_offset(),
-                              Object::dynamic_type(), kArrayCid));
+            BuildNativeGetter(node, NativeFieldDesc::LinkedHashMap_data()));
       }
       case MethodRecognizer::kLinkedHashMap_setData: {
         return ReturnDefinition(DoNativeSetterStoreValue(
             node, LinkedHashMap::data_offset(), kEmitStoreBarrier));
       }
       case MethodRecognizer::kLinkedHashMap_getHashMask: {
-        return ReturnDefinition(
-            BuildNativeGetter(node, kind, LinkedHashMap::hash_mask_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid));
+        return ReturnDefinition(BuildNativeGetter(
+            node, NativeFieldDesc::LinkedHashMap_hash_mask()));
       }
       case MethodRecognizer::kLinkedHashMap_setHashMask: {
         // Smi field; no barrier needed.
@@ -3370,9 +3359,8 @@
             node, LinkedHashMap::hash_mask_offset(), kNoStoreBarrier));
       }
       case MethodRecognizer::kLinkedHashMap_getUsedData: {
-        return ReturnDefinition(
-            BuildNativeGetter(node, kind, LinkedHashMap::used_data_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid));
+        return ReturnDefinition(BuildNativeGetter(
+            node, NativeFieldDesc::LinkedHashMap_used_data()));
       }
       case MethodRecognizer::kLinkedHashMap_setUsedData: {
         // Smi field; no barrier needed.
@@ -3380,9 +3368,9 @@
             node, LinkedHashMap::used_data_offset(), kNoStoreBarrier));
       }
       case MethodRecognizer::kLinkedHashMap_getDeletedKeys: {
-        return ReturnDefinition(
-            BuildNativeGetter(node, kind, LinkedHashMap::deleted_keys_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid));
+        return ReturnDefinition(BuildNativeGetter(
+            node, NativeFieldDesc::Get(
+                      NativeFieldDesc::kLinkedHashMap_deleted_keys)));
       }
       case MethodRecognizer::kLinkedHashMap_setDeletedKeys: {
         // Smi field; no barrier needed.
diff --git a/runtime/vm/compiler/frontend/flow_graph_builder.h b/runtime/vm/compiler/frontend/flow_graph_builder.h
index 057cf56..94dc758 100644
--- a/runtime/vm/compiler/frontend/flow_graph_builder.h
+++ b/runtime/vm/compiler/frontend/flow_graph_builder.h
@@ -302,10 +302,7 @@
                              TokenPosition token_pos);
   LoadLocalInstr* BuildLoadThisVar(LocalScope* scope, TokenPosition token_pos);
   LoadFieldInstr* BuildNativeGetter(NativeBodyNode* node,
-                                    MethodRecognizer::Kind kind,
-                                    intptr_t offset,
-                                    const Type& type,
-                                    intptr_t class_id);
+                                    const NativeFieldDesc* native_field);
   // Assumes setter parameter is named 'value'. Returns null constant.
   ConstantInstr* DoNativeSetterStoreValue(NativeBodyNode* node,
                                           intptr_t offset,
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index eb7453a..c70ad59 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -730,7 +730,7 @@
   TargetEntryInstr* neq_entry;
 
   test += LoadArgDescriptor();
-  test += LoadField(ArgumentsDescriptor::type_args_len_offset());
+  test += LoadNativeField(NativeFieldDesc::ArgumentsDescriptor_type_args_len());
   test += IntConstant(num_type_args);
   test += BranchIfEqual(&eq_entry, &neq_entry);
 
@@ -854,16 +854,10 @@
   return Fragment(instr);
 }
 
-Fragment FlowGraphBuilder::LoadNativeField(MethodRecognizer::Kind kind,
-                                           intptr_t offset,
-                                           const Type& type,
-                                           intptr_t class_id,
-                                           bool is_immutable) {
+Fragment BaseFlowGraphBuilder::LoadNativeField(
+    const NativeFieldDesc* native_field) {
   LoadFieldInstr* load =
-      new (Z) LoadFieldInstr(Pop(), offset, type, TokenPosition::kNoSource);
-  load->set_recognized_kind(kind);
-  load->set_result_cid(class_id);
-  load->set_is_immutable(is_immutable);
+      new (Z) LoadFieldInstr(Pop(), native_field, TokenPosition::kNoSource);
   Push(load);
   return Fragment(load);
 }
@@ -1449,10 +1443,7 @@
     case MethodRecognizer::kStringBaseLength:
     case MethodRecognizer::kStringBaseIsEmpty:
       body += LoadLocal(scopes_->this_variable);
-      body += LoadNativeField(MethodRecognizer::kStringBaseLength,
-                              String::length_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid,
-                              /* is_immutable = */ true);
+      body += LoadNativeField(NativeFieldDesc::String_length());
       if (kind == MethodRecognizer::kStringBaseIsEmpty) {
         body += IntConstant(0);
         body += StrictCompare(Token::kEQ_STRICT);
@@ -1460,21 +1451,16 @@
       break;
     case MethodRecognizer::kGrowableArrayLength:
       body += LoadLocal(scopes_->this_variable);
-      body += LoadNativeField(kind, GrowableObjectArray::length_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid);
+      body += LoadNativeField(NativeFieldDesc::GrowableObjectArray_length());
       break;
     case MethodRecognizer::kObjectArrayLength:
     case MethodRecognizer::kImmutableArrayLength:
       body += LoadLocal(scopes_->this_variable);
-      body +=
-          LoadNativeField(kind, Array::length_offset(),
-                          Type::ZoneHandle(Z, Type::SmiType()), kSmiCid, true);
+      body += LoadNativeField(NativeFieldDesc::Array_length());
       break;
     case MethodRecognizer::kTypedDataLength:
       body += LoadLocal(scopes_->this_variable);
-      body +=
-          LoadNativeField(kind, TypedData::length_offset(),
-                          Type::ZoneHandle(Z, Type::SmiType()), kSmiCid, true);
+      body += LoadNativeField(NativeFieldDesc::TypedData_length());
       break;
     case MethodRecognizer::kClassIDgetID:
       body += LoadLocal(LookupVariable(first_positional_offset));
@@ -1483,9 +1469,7 @@
     case MethodRecognizer::kGrowableArrayCapacity:
       body += LoadLocal(scopes_->this_variable);
       body += LoadField(GrowableObjectArray::data_offset(), kArrayCid);
-      body += LoadNativeField(MethodRecognizer::kObjectArrayLength,
-                              Array::length_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid);
+      body += LoadNativeField(NativeFieldDesc::Array_length());
       break;
     case MethodRecognizer::kListFactory: {
       // factory List<E>([int length]) {
@@ -1559,8 +1543,7 @@
       break;
     case MethodRecognizer::kLinkedHashMap_getIndex:
       body += LoadLocal(scopes_->this_variable);
-      body += LoadNativeField(kind, LinkedHashMap::index_offset(),
-                              Object::dynamic_type(), kTypedDataUint32ArrayCid);
+      body += LoadNativeField(NativeFieldDesc::LinkedHashMap_index());
       break;
     case MethodRecognizer::kLinkedHashMap_setIndex:
       body += LoadLocal(scopes_->this_variable);
@@ -1571,8 +1554,7 @@
       break;
     case MethodRecognizer::kLinkedHashMap_getData:
       body += LoadLocal(scopes_->this_variable);
-      body += LoadNativeField(kind, LinkedHashMap::data_offset(),
-                              Object::dynamic_type(), kArrayCid);
+      body += LoadNativeField(NativeFieldDesc::LinkedHashMap_data());
       break;
     case MethodRecognizer::kLinkedHashMap_setData:
       body += LoadLocal(scopes_->this_variable);
@@ -1583,8 +1565,7 @@
       break;
     case MethodRecognizer::kLinkedHashMap_getHashMask:
       body += LoadLocal(scopes_->this_variable);
-      body += LoadNativeField(kind, LinkedHashMap::hash_mask_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid);
+      body += LoadNativeField(NativeFieldDesc::LinkedHashMap_hash_mask());
       break;
     case MethodRecognizer::kLinkedHashMap_setHashMask:
       body += LoadLocal(scopes_->this_variable);
@@ -1596,8 +1577,7 @@
       break;
     case MethodRecognizer::kLinkedHashMap_getUsedData:
       body += LoadLocal(scopes_->this_variable);
-      body += LoadNativeField(kind, LinkedHashMap::used_data_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid);
+      body += LoadNativeField(NativeFieldDesc::LinkedHashMap_used_data());
       break;
     case MethodRecognizer::kLinkedHashMap_setUsedData:
       body += LoadLocal(scopes_->this_variable);
@@ -1609,8 +1589,7 @@
       break;
     case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
       body += LoadLocal(scopes_->this_variable);
-      body += LoadNativeField(kind, LinkedHashMap::deleted_keys_offset(),
-                              Type::ZoneHandle(Z, Type::SmiType()), kSmiCid);
+      body += LoadNativeField(NativeFieldDesc::LinkedHashMap_deleted_keys());
       break;
     case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
       body += LoadLocal(scopes_->this_variable);
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h
index 27d0f3a..a305b30 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.h
+++ b/runtime/vm/compiler/frontend/kernel_to_il.h
@@ -396,6 +396,7 @@
         pending_argument_count_(0) {}
 
   Fragment LoadField(intptr_t offset, intptr_t class_id = kDynamicCid);
+  Fragment LoadNativeField(const NativeFieldDesc* native_field);
   Fragment LoadIndexed(intptr_t index_scale);
 
   void SetTempIndex(Definition* definition);
@@ -604,11 +605,6 @@
   Fragment LoadClassId();
   Fragment LoadField(intptr_t offset, intptr_t class_id = kDynamicCid);
   Fragment LoadField(const Field& field);
-  Fragment LoadNativeField(MethodRecognizer::Kind kind,
-                           intptr_t offset,
-                           const Type& type,
-                           intptr_t class_id,
-                           bool is_immutable = false);
   Fragment LoadLocal(LocalVariable* variable);
   Fragment InitStaticField(const Field& field);
   Fragment LoadStaticField();
diff --git a/runtime/vm/compiler/frontend/prologue_builder.cc b/runtime/vm/compiler/frontend/prologue_builder.cc
index 3e32dbc..117f76c 100644
--- a/runtime/vm/compiler/frontend/prologue_builder.cc
+++ b/runtime/vm/compiler/frontend/prologue_builder.cc
@@ -87,7 +87,8 @@
   // If expect_type_args, a non-zero length must match the declaration length.
   TargetEntryInstr *then, *fail;
   check_type_args += LoadArgDescriptor();
-  check_type_args += LoadField(ArgumentsDescriptor::type_args_len_offset());
+  check_type_args += LoadNativeField(NativeFieldDesc::Get(
+      NativeFieldDesc::kArgumentsDescriptor_type_args_len));
   if (expect_type_args) {
     JoinEntryInstr* join2 = BuildJoinEntry();