[vm,aot,bytecode] Handle inferred-type metadata when generating bytecode

Change-Id: Ifadb25c782db5c95025e7275daf0676abd73cf49
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/120341
Reviewed-by: RĂ©gis Crelier <regis@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index 0724be1..6b64e85 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -56,6 +56,8 @@
 import '../metadata/bytecode.dart';
 import '../metadata/direct_call.dart'
     show DirectCallMetadata, DirectCallMetadataRepository;
+import '../metadata/inferred_type.dart'
+    show InferredType, InferredTypeMetadataRepository;
 import '../metadata/procedure_attributes.dart'
     show ProcedureAttributesMetadata, ProcedureAttributesMetadataRepository;
 
@@ -126,6 +128,8 @@
   Map<TreeNode, DirectCallMetadata> directCallMetadata;
   ProcedureAttributesMetadataRepository procedureAttributesMetadataRepository;
   ProcedureAttributesMetadata procedureAttributesMetadata;
+  Map<TreeNode, InferredType> inferredTypeMetadata;
+  List<Constant> inferredTypesAttribute;
 
   List<ClassDeclaration> classDeclarations;
   List<FieldDeclaration> fieldDeclarations;
@@ -182,6 +186,9 @@
 
     procedureAttributesMetadataRepository =
         component.metadata[ProcedureAttributesMetadataRepository.repositoryTag];
+
+    inferredTypeMetadata = component
+        .metadata[InferredTypeMetadataRepository.repositoryTag]?.mapping;
   }
 
   @override
@@ -410,17 +417,23 @@
   }
 
   ObjectHandle getMemberAttributes(Member member) {
-    if (procedureAttributesMetadata == null) {
+    if (procedureAttributesMetadata == null && inferredTypesAttribute == null) {
       return null;
     }
-    final procedureAttributes = procedureAttributesMetadataRepository
-        .getBytecodeAttribute(procedureAttributesMetadata);
     // List of pairs (tag, value).
-    final attrs = ListConstant(const DynamicType(), <Constant>[
-      StringConstant(ProcedureAttributesMetadataRepository.repositoryTag),
-      procedureAttributes,
-    ]);
-    return objectTable.getHandle(attrs);
+    final attrs = <Constant>[];
+    if (procedureAttributesMetadata != null) {
+      final attribute = procedureAttributesMetadataRepository
+          .getBytecodeAttribute(procedureAttributesMetadata);
+      attrs.add(
+          StringConstant(ProcedureAttributesMetadataRepository.repositoryTag));
+      attrs.add(attribute);
+    }
+    if (inferredTypesAttribute != null) {
+      attrs.add(StringConstant(InferredTypeMetadataRepository.repositoryTag));
+      attrs.add(ListConstant(const DynamicType(), inferredTypesAttribute));
+    }
+    return objectTable.getHandle(ListConstant(const DynamicType(), attrs));
   }
 
   // Insert annotations for the function and its parameters into the annotations
@@ -1180,7 +1193,7 @@
       bool isSet: false,
       bool isDynamicForwarder: false,
       bool isUnchecked: false,
-      TreeNode context}) {
+      TreeNode node}) {
     assert(!isGet || !isSet);
     final kind = isGet
         ? InvocationKind.getter
@@ -1188,7 +1201,10 @@
     final cpIndex = cp.addDirectCall(kind, target, argDesc, isDynamicForwarder);
 
     if (totalArgCount >= argumentsLimit) {
-      throw new TooManyArgumentsException(context.fileOffset);
+      throw new TooManyArgumentsException(node.fileOffset);
+    }
+    if (inferredTypeMetadata != null && node != null) {
+      _appendInferredType(node, asm.offset);
     }
     if (isUnchecked) {
       asm.emitUncheckedDirectCall(cpIndex, totalArgCount);
@@ -1201,7 +1217,7 @@
       {bool hasReceiver: false,
       bool isFactory: false,
       bool isUnchecked: false,
-      TreeNode context}) {
+      TreeNode node}) {
     final argDesc = objectTable.getArgDescHandleByArguments(args,
         hasReceiver: hasReceiver, isFactory: isFactory);
 
@@ -1216,7 +1232,7 @@
     }
 
     _genDirectCall(target, argDesc, totalArgCount,
-        isUnchecked: isUnchecked, context: context);
+        isUnchecked: isUnchecked, node: node);
   }
 
   void _genTypeArguments(List<DartType> typeArgs, {Class instantiatingClass}) {
@@ -1553,6 +1569,28 @@
     procedureAttributesMetadata = procedureAttributesMetadataRepository != null
         ? procedureAttributesMetadataRepository.mapping[node]
         : null;
+
+    if (inferredTypeMetadata != null) {
+      if (node is Field) {
+        // Field type is at PC = -1.
+        _appendInferredType(node, -1);
+      } else if (enclosingFunction != null && hasCode) {
+        assert(node is Procedure || node is Constructor);
+        // Parameter types are at PC = -N,..,-1 where N - number of declared
+        // (explicit) parameters.
+        int i = -(enclosingFunction.positionalParameters.length +
+            enclosingFunction.namedParameters.length);
+        for (var v in enclosingFunction.positionalParameters) {
+          _appendInferredType(v, i);
+          ++i;
+        }
+        for (var v in enclosingFunction.namedParameters) {
+          _appendInferredType(v, i);
+          ++i;
+        }
+      }
+    }
+
     if (!hasCode) {
       return;
     }
@@ -1596,6 +1634,32 @@
     _genEqualsOperatorNullHandling(node);
   }
 
+  void _appendInferredType(TreeNode node, int pc) {
+    final InferredType md = inferredTypeMetadata[node];
+    if (md == null) {
+      return;
+    }
+    inferredTypesAttribute ??= <Constant>[];
+    // List of triplets (PC, concreteClass, flags).
+    // Verify that PCs are monotonically increasing.
+    assert(inferredTypesAttribute.isEmpty ||
+        (inferredTypesAttribute[inferredTypesAttribute.length - 3]
+                    as IntConstant)
+                .value <
+            pc);
+    inferredTypesAttribute.add(IntConstant(pc));
+    Class concreteClass = md.concreteClass;
+    // VM uses more specific function type and doesn't expect to
+    // see inferred _Closure class.
+    if (concreteClass != null && concreteClass != closureClass) {
+      inferredTypesAttribute
+          .add(TypeLiteralConstant(coreTypes.legacyRawType(concreteClass)));
+    } else {
+      inferredTypesAttribute.add(NullConstant());
+    }
+    inferredTypesAttribute.add(IntConstant(md.flags));
+  }
+
   // Generate additional code for 'operator ==' to handle nulls.
   void _genEqualsOperatorNullHandling(Member member) {
     if (member.name.name != '==' ||
@@ -1693,6 +1757,8 @@
     savedAssemblers = null;
     hasErrors = false;
     procedureAttributesMetadata = null;
+    inferredTypeMetadata = null;
+    inferredTypesAttribute = null;
   }
 
   SourcePositions finalizeSourcePositions() {
@@ -2767,7 +2833,7 @@
         new Arguments(node.arguments.positional, named: node.arguments.named)
           ..parent = node;
     _genArguments(null, args);
-    _genDirectCallWithArgs(node.target, args, hasReceiver: true, context: node);
+    _genDirectCallWithArgs(node.target, args, hasReceiver: true, node: node);
     asm.emitDrop1();
   }
 
@@ -2777,7 +2843,7 @@
     _genArguments(node.receiver, args);
     final target = node.target;
     if (target is Procedure && !target.isGetter && !target.isSetter) {
-      _genDirectCallWithArgs(target, args, hasReceiver: true, context: node);
+      _genDirectCallWithArgs(target, args, hasReceiver: true, node: node);
     } else {
       throw new UnsupportedOperationError(
           'Unsupported DirectMethodInvocation with target ${target.runtimeType} $target');
@@ -2789,7 +2855,8 @@
     _generateNode(node.receiver);
     final target = node.target;
     if (target is Field || (target is Procedure && target.isGetter)) {
-      _genDirectCall(target, objectTable.getArgDescHandle(1), 1, isGet: true);
+      _genDirectCall(target, objectTable.getArgDescHandle(1), 1,
+          isGet: true, node: node);
     } else {
       throw new UnsupportedOperationError(
           'Unsupported DirectPropertyGet with ${target.runtimeType} $target');
@@ -2810,7 +2877,8 @@
 
     final target = node.target;
     assert(target is Field || (target is Procedure && target.isSetter));
-    _genDirectCall(target, objectTable.getArgDescHandle(2), 2, isSet: true);
+    _genDirectCall(target, objectTable.getArgDescHandle(2), 2,
+        isSet: true, node: node);
     asm.emitDrop1();
 
     if (hasResult) {
@@ -3031,7 +3099,14 @@
     asm.emitSpecializedBytecode(opcode);
   }
 
+  bool _isUncheckedCall(
+          Node node, Member interfaceTarget, Expression receiver) =>
+      isUncheckedCall(interfaceTarget, receiver, typeEnvironment) ||
+      (inferredTypeMetadata != null &&
+          inferredTypeMetadata[node]?.skipCheck == true);
+
   void _genInstanceCall(
+      Node node,
       InvocationKind invocationKind,
       Member interfaceTarget,
       Name targetName,
@@ -3040,7 +3115,11 @@
       ObjectHandle argDesc) {
     final isDynamic = interfaceTarget == null;
     final isUnchecked = invocationKind != InvocationKind.getter &&
-        isUncheckedCall(interfaceTarget, receiver, typeEnvironment);
+        _isUncheckedCall(node, interfaceTarget, receiver);
+
+    if (inferredTypeMetadata != null) {
+      _appendInferredType(node, asm.offset);
+    }
 
     if (invocationKind != InvocationKind.getter && !isDynamic && !isUnchecked) {
       final staticReceiverType = getStaticType(receiver, typeEnvironment);
@@ -3118,11 +3197,13 @@
     if (directCall != null) {
       final isDynamicForwarder = (interfaceTarget == null);
       final isUnchecked =
-          isUncheckedCall(interfaceTarget, node.receiver, typeEnvironment);
+          _isUncheckedCall(node, interfaceTarget, node.receiver);
       _genDirectCall(directCall.target, argDesc, totalArgCount,
-          isDynamicForwarder: isDynamicForwarder, isUnchecked: isUnchecked);
+          isDynamicForwarder: isDynamicForwarder,
+          isUnchecked: isUnchecked,
+          node: node);
     } else {
-      _genInstanceCall(InvocationKind.method, interfaceTarget, node.name,
+      _genInstanceCall(node, InvocationKind.method, interfaceTarget, node.name,
           node.receiver, totalArgCount, argDesc);
     }
   }
@@ -3142,10 +3223,10 @@
         asm.emitCheckReceiverForNull(
             cp.addSelectorName(node.name, InvocationKind.getter));
       }
-      _genDirectCall(directCall.target, argDesc, 1, isGet: true);
+      _genDirectCall(directCall.target, argDesc, 1, isGet: true, node: node);
     } else {
-      _genInstanceCall(InvocationKind.getter, node.interfaceTarget, node.name,
-          node.receiver, 1, argDesc);
+      _genInstanceCall(node, InvocationKind.getter, node.interfaceTarget,
+          node.name, node.receiver, 1, argDesc);
     }
   }
 
@@ -3178,14 +3259,15 @@
     if (directCall != null) {
       final isDynamicForwarder = (node.interfaceTarget == null);
       final isUnchecked =
-          isUncheckedCall(node.interfaceTarget, node.receiver, typeEnvironment);
+          _isUncheckedCall(node, node.interfaceTarget, node.receiver);
       _genDirectCall(directCall.target, argDesc, numArguments,
           isSet: true,
           isDynamicForwarder: isDynamicForwarder,
-          isUnchecked: isUnchecked);
+          isUnchecked: isUnchecked,
+          node: node);
     } else {
-      _genInstanceCall(InvocationKind.setter, node.interfaceTarget, node.name,
-          node.receiver, numArguments, argDesc);
+      _genInstanceCall(node, InvocationKind.setter, node.interfaceTarget,
+          node.name, node.receiver, numArguments, argDesc);
     }
 
     asm.emitDrop1();
@@ -3214,7 +3296,7 @@
     }
     _genArguments(new ThisExpression(), args);
     _genDirectCallWithArgs(target, args,
-        hasReceiver: true, isUnchecked: true, context: node);
+        hasReceiver: true, isUnchecked: true, node: node);
   }
 
   @override
@@ -3228,7 +3310,8 @@
       return;
     }
     _genPushReceiver();
-    _genDirectCall(target, objectTable.getArgDescHandle(1), 1, isGet: true);
+    _genDirectCall(target, objectTable.getArgDescHandle(1), 1,
+        isGet: true, node: node);
   }
 
   @override
@@ -3252,7 +3335,7 @@
 
       assert(target is Field || (target is Procedure && target.isSetter));
       _genDirectCall(target, objectTable.getArgDescHandle(2), 2,
-          isSet: true, isUnchecked: true);
+          isSet: true, isUnchecked: true, node: node);
     }
 
     asm.emitDrop1();
@@ -3321,11 +3404,13 @@
       } else if (_hasTrivialInitializer(target)) {
         asm.emitLoadStatic(cp.addStaticField(target));
       } else {
-        _genDirectCall(target, objectTable.getArgDescHandle(0), 0, isGet: true);
+        _genDirectCall(target, objectTable.getArgDescHandle(0), 0,
+            isGet: true, node: node);
       }
     } else if (target is Procedure) {
       if (target.isGetter) {
-        _genDirectCall(target, objectTable.getArgDescHandle(0), 0, isGet: true);
+        _genDirectCall(target, objectTable.getArgDescHandle(0), 0,
+            isGet: true, node: node);
       } else if (target.isFactory || target.isRedirectingFactoryConstructor) {
         throw 'Unexpected target for StaticGet: factory $target';
       } else {
@@ -3367,7 +3452,7 @@
     }
     _genArguments(null, args);
     _genDirectCallWithArgs(target, args,
-        isFactory: target.isFactory, context: node);
+        isFactory: target.isFactory, node: node);
   }
 
   @override
@@ -3389,7 +3474,8 @@
       int cpIndex = cp.addStaticField(target);
       asm.emitStoreStaticTOS(cpIndex);
     } else {
-      _genDirectCall(target, objectTable.getArgDescHandle(1), 1, isSet: true);
+      _genDirectCall(target, objectTable.getArgDescHandle(1), 1,
+          isSet: true, node: node);
       asm.emitDrop1();
     }
   }
@@ -4242,7 +4328,7 @@
     final args = node.arguments;
     assert(args.types.isEmpty);
     _genArguments(new ThisExpression(), args);
-    _genDirectCallWithArgs(node.target, args, hasReceiver: true, context: node);
+    _genDirectCallWithArgs(node.target, args, hasReceiver: true, node: node);
     asm.emitDrop1();
   }
 
@@ -4260,7 +4346,7 @@
       }
     }
     assert(target != null);
-    _genDirectCallWithArgs(target, args, hasReceiver: true, context: node);
+    _genDirectCallWithArgs(target, args, hasReceiver: true, node: node);
     asm.emitDrop1();
   }
 
diff --git a/pkg/vm/lib/metadata/inferred_type.dart b/pkg/vm/lib/metadata/inferred_type.dart
index c22b155..432f693 100644
--- a/pkg/vm/lib/metadata/inferred_type.dart
+++ b/pkg/vm/lib/metadata/inferred_type.dart
@@ -47,6 +47,8 @@
   bool get isInt => (_flags & flagInt) != 0;
   bool get skipCheck => (_flags & flagSkipCheck) != 0;
 
+  int get flags => _flags;
+
   @override
   String toString() {
     final base =
@@ -65,8 +67,10 @@
 
 /// Repository for [InferredType].
 class InferredTypeMetadataRepository extends MetadataRepository<InferredType> {
+  static const String repositoryTag = 'vm.inferred-type.metadata';
+
   @override
-  final String tag = 'vm.inferred-type.metadata';
+  String get tag => repositoryTag;
 
   @override
   final Map<TreeNode, InferredType> mapping = <TreeNode, InferredType>{};
diff --git a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc
index be09321..6edaba1 100644
--- a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc
+++ b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc
@@ -153,9 +153,6 @@
   if (is_generating_interpreter()) {
     UNIMPLEMENTED();  // TODO(alexmarkov): interpreter
   } else {
-    // TODO(alexmarkov): Make table of local variables in bytecode and
-    // propagate type, name and positions.
-
     ASSERT(local_vars_.is_empty());
 
     const intptr_t num_bytecode_locals = frame_size.value();
@@ -230,8 +227,23 @@
   const AbstractType& type =
       AbstractType::ZoneHandle(Z, function().ParameterTypeAt(param_index));
 
-  LocalVariable* param_var = new (Z) LocalVariable(
-      TokenPosition::kNoSource, TokenPosition::kNoSource, name, type);
+  CompileType* param_type = nullptr;
+  if (!inferred_types_attribute_.IsNull()) {
+    // Parameter types are assigned to synthetic PCs = -N,..,-1
+    // where N is number of parameters.
+    const intptr_t pc = -function().NumParameters() + param_index;
+    // Search from the beginning as parameters may be declared in arbitrary
+    // order.
+    inferred_types_index_ = 0;
+    const InferredTypeMetadata inferred_type = GetInferredType(pc);
+    if (!inferred_type.IsTrivial()) {
+      param_type = new (Z) CompileType(inferred_type.ToCompileType(Z));
+    }
+  }
+
+  LocalVariable* param_var =
+      new (Z) LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
+                            name, type, param_type);
   param_var->set_index(var_index);
 
   if (!function().IsNonImplicitClosureFunction() &&
@@ -448,6 +460,31 @@
   return arguments;
 }
 
+InferredTypeMetadata BytecodeFlowGraphBuilder::GetInferredType(intptr_t pc) {
+  ASSERT(!inferred_types_attribute_.IsNull());
+  intptr_t i = inferred_types_index_;
+  const intptr_t len = inferred_types_attribute_.Length();
+  for (; i < len; i += InferredTypeBytecodeAttribute::kNumElements) {
+    ASSERT(i + InferredTypeBytecodeAttribute::kNumElements <= len);
+    const intptr_t attr_pc =
+        InferredTypeBytecodeAttribute::GetPCAt(inferred_types_attribute_, i);
+    if (attr_pc == pc) {
+      const InferredTypeMetadata result =
+          InferredTypeBytecodeAttribute::GetInferredTypeAt(
+              Z, inferred_types_attribute_, i);
+      // Found. Next time, continue search at the next entry.
+      inferred_types_index_ = i + InferredTypeBytecodeAttribute::kNumElements;
+      return result;
+    }
+    if (attr_pc > pc) {
+      break;
+    }
+  }
+  // Not found. Next time, continue search at the last inspected entry.
+  inferred_types_index_ = i;
+  return InferredTypeMetadata(kDynamicCid, InferredTypeMetadata::kFlagNullable);
+}
+
 void BytecodeFlowGraphBuilder::PropagateStackState(intptr_t target_pc) {
   if (is_generating_interpreter() || IsStackEmpty()) {
     return;
@@ -861,7 +898,14 @@
     call->set_entry_kind(Code::EntryKind::kUnchecked);
   }
 
-  call->InitResultType(Z);
+  if (!call->InitResultType(Z)) {
+    if (!inferred_types_attribute_.IsNull()) {
+      const InferredTypeMetadata result_type = GetInferredType(pc_);
+      if (!result_type.IsTrivial()) {
+        call->SetResultType(Z, result_type.ToCompileType(Z));
+      }
+    }
+  }
 
   code_ <<= call;
   B->Push(call);
@@ -928,7 +972,12 @@
       Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), checked_argument_count,
       *ic_data_array_, B->GetNextDeoptId(), interface_target);
 
-  // TODO(alexmarkov): add type info - call->SetResultType()
+  if (!inferred_types_attribute_.IsNull()) {
+    const InferredTypeMetadata result_type = GetInferredType(pc_);
+    if (!result_type.IsTrivial()) {
+      call->SetResultType(Z, result_type.ToCompileType(Z));
+    }
+  }
 
   if (is_unchecked_call) {
     call->set_entry_kind(Code::EntryKind::kUnchecked);
@@ -991,6 +1040,14 @@
       Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), position_,
       B->GetNextDeoptId(), Code::EntryKind::kUnchecked);
 
+  // TODO(alexmarkov): use inferred result type for ClosureCallInstr
+  //  if (!inferred_types_attribute_.IsNull()) {
+  //    const InferredTypeMetadata result_type = GetInferredType(pc_);
+  //    if (!result_type.IsTrivial()) {
+  //      call->SetResultType(Z, result_type.ToCompileType(Z));
+  //    }
+  //  }
+
   code_ <<= call;
   B->Push(call);
 }
@@ -1025,7 +1082,12 @@
       Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), checked_argument_count,
       *ic_data_array_, B->GetNextDeoptId(), interface_target);
 
-  // TODO(alexmarkov): add type info - call->SetResultType()
+  if (!inferred_types_attribute_.IsNull()) {
+    const InferredTypeMetadata result_type = GetInferredType(pc_);
+    if (!result_type.IsTrivial()) {
+      call->SetResultType(Z, result_type.ToCompileType(Z));
+    }
+  }
 
   code_ <<= call;
   B->Push(call);
@@ -2171,6 +2233,9 @@
 
   CollectControlFlow(descriptors, handlers, graph_entry_);
 
+  inferred_types_attribute_ ^= BytecodeReader::GetBytecodeAttribute(
+      function(), Symbols::vm_inferred_type_metadata());
+
   kernel::BytecodeSourcePositionsIterator source_pos_iter(Z, bytecode);
   bool update_position = source_pos_iter.MoveNext();
 
diff --git a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h
index 814b8df..6b1ea18 100644
--- a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h
+++ b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h
@@ -7,6 +7,7 @@
 
 #include "vm/compiler/backend/il.h"
 #include "vm/compiler/frontend/base_flow_graph_builder.h"
+#include "vm/compiler/frontend/kernel_translation_helper.h"  // For InferredTypeMetadata
 #include "vm/constants_kbc.h"
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
@@ -42,7 +43,8 @@
         stacktrace_var_(nullptr),
         scratch_var_(nullptr),
         prologue_info_(-1, -1),
-        throw_no_such_method_(nullptr) {}
+        throw_no_such_method_(nullptr),
+        inferred_types_attribute_(Array::Handle(zone_)) {}
 
   FlowGraph* BuildGraph();
 
@@ -158,6 +160,7 @@
   intptr_t GetStackDepth() const;
   bool IsStackEmpty() const;
   ArgumentArray GetArguments(int count);
+  InferredTypeMetadata GetInferredType(intptr_t pc);
   void PropagateStackState(intptr_t target_pc);
   void DropUnusedValuesFromStack();
   void BuildJumpIfStrictCompare(Token::Kind cmp_kind);
@@ -236,6 +239,8 @@
   bool build_debug_step_checks_ = false;
   bool seen_parameters_scope_ = false;
   BytecodeScope* current_scope_ = nullptr;
+  Array& inferred_types_attribute_;
+  intptr_t inferred_types_index_ = 0;
 };
 
 }  // namespace kernel
diff --git a/runtime/vm/compiler/frontend/bytecode_reader.cc b/runtime/vm/compiler/frontend/bytecode_reader.cc
index 7d5970b..a888b26 100644
--- a/runtime/vm/compiler/frontend/bytecode_reader.cc
+++ b/runtime/vm/compiler/frontend/bytecode_reader.cc
@@ -1885,6 +1885,26 @@
   bool present = map.UpdateOrInsert(key, value);
   ASSERT(!present);
   I->object_store()->set_bytecode_attributes(map.Release());
+
+  if (key.IsField()) {
+    const Field& field = Field::Cast(key);
+    const auto& inferred_type_attr =
+        Array::CheckedHandle(Z, BytecodeReader::GetBytecodeAttribute(
+                                    key, Symbols::vm_inferred_type_metadata()));
+
+    if (!inferred_type_attr.IsNull() &&
+        (InferredTypeBytecodeAttribute::GetPCAt(inferred_type_attr, 0) ==
+         InferredTypeBytecodeAttribute::kFieldTypePC)) {
+      const InferredTypeMetadata type =
+          InferredTypeBytecodeAttribute::GetInferredTypeAt(
+              Z, inferred_type_attr, 0);
+      if (!type.IsTrivial()) {
+        field.set_guarded_cid(type.cid);
+        field.set_is_nullable(type.IsNullable());
+        field.set_guarded_list_length(Field::kNoFixedLength);
+      }
+    }
+  }
 }
 
 void BytecodeReaderHelper::ReadMembers(const Class& cls, bool discard_fields) {
@@ -3499,6 +3519,21 @@
   return Object::null();
 }
 
+InferredTypeMetadata InferredTypeBytecodeAttribute::GetInferredTypeAt(
+    Zone* zone,
+    const Array& attr,
+    intptr_t index) {
+  ASSERT(index + kNumElements <= attr.Length());
+  const auto& type = AbstractType::CheckedHandle(zone, attr.At(index + 1));
+  const intptr_t flags = Smi::Value(Smi::RawCast(attr.At(index + 2)));
+  if (!type.IsNull()) {
+    intptr_t cid = Type::Cast(type).type_class_id();
+    return InferredTypeMetadata(cid, flags);
+  } else {
+    return InferredTypeMetadata(kDynamicCid, flags);
+  }
+}
+
 #if !defined(PRODUCT)
 RawLocalVarDescriptors* BytecodeReader::ComputeLocalVarDescriptors(
     Zone* zone,
diff --git a/runtime/vm/compiler/frontend/bytecode_reader.h b/runtime/vm/compiler/frontend/bytecode_reader.h
index d5ad36d..418d021 100644
--- a/runtime/vm/compiler/frontend/bytecode_reader.h
+++ b/runtime/vm/compiler/frontend/bytecode_reader.h
@@ -370,6 +370,26 @@
 #endif
 };
 
+class InferredTypeBytecodeAttribute : public AllStatic {
+ public:
+  // Number of array elements per entry in InferredType bytecode
+  // attribute (PC, type, flags).
+  static constexpr intptr_t kNumElements = 3;
+
+  // Field type is the first entry with PC = -1.
+  static constexpr intptr_t kFieldTypePC = -1;
+
+  // Returns PC at given index.
+  static intptr_t GetPCAt(const Array& attr, intptr_t index) {
+    return Smi::Value(Smi::RawCast(attr.At(index)));
+  }
+
+  // Returns InferredType metadata at given index.
+  static InferredTypeMetadata GetInferredTypeAt(Zone* zone,
+                                                const Array& attr,
+                                                intptr_t index);
+};
+
 class BytecodeSourcePositionsIterator : ValueObject {
  public:
   // These constants should match corresponding constants in class
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index 9d06f55..c95b589 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -462,6 +462,7 @@
   V(vm_prefer_inline, "vm:prefer-inline")                                      \
   V(vm_entry_point, "vm:entry-point")                                          \
   V(vm_exact_result_type, "vm:exact-result-type")                              \
+  V(vm_inferred_type_metadata, "vm.inferred-type.metadata")                    \
   V(vm_never_inline, "vm:never-inline")                                        \
   V(vm_non_nullable_result_type, "vm:non-nullable-result-type")                \
   V(vm_trace_entrypoints, "vm:testing.unsafe.trace-entrypoints-fn")            \