diff --git a/src/compiler/common-operator.cc b/src/compiler/common-operator.cc
index 12a06530e3..736a961e40 100644
--- a/src/compiler/common-operator.cc
+++ b/src/compiler/common-operator.cc
@@ -235,6 +235,12 @@ OsrGuardType OsrGuardTypeOf(Operator const* op) {
   return OpParameter<OsrGuardType>(op);
 }
 
+ZoneVector<MachineType> const* MachineTypesOf(Operator const* op) {
+  DCHECK(op->opcode() == IrOpcode::kTypedObjectState ||
+         op->opcode() == IrOpcode::kTypedStateValues);
+  return OpParameter<const ZoneVector<MachineType>*>(op);
+}
+
 #define CACHED_OP_LIST(V)                                                     \
   V(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1)                              \
   V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1)                             \
@@ -1004,23 +1010,31 @@ const Operator* CommonOperatorBuilder::StateValues(int arguments) {
       arguments, 0, 0, 1, 0, 0);                // counts
 }
 
-
-const Operator* CommonOperatorBuilder::ObjectState(int pointer_slots, int id) {
-  return new (zone()) Operator1<int>(           // --
-      IrOpcode::kObjectState, Operator::kPure,  // opcode
-      "ObjectState",                            // name
-      pointer_slots, 0, 0, 1, 0, 0, id);        // counts
-}
-
-
 const Operator* CommonOperatorBuilder::TypedStateValues(
     const ZoneVector<MachineType>* types) {
   return new (zone()) Operator1<const ZoneVector<MachineType>*>(  // --
       IrOpcode::kTypedStateValues, Operator::kPure,               // opcode
       "TypedStateValues",                                         // name
-      static_cast<int>(types->size()), 0, 0, 1, 0, 0, types);     // counts
+      static_cast<int>(types->size()), 0, 0, 1, 0, 0,             // counts
+      types);                                                     // parameter
 }
 
+const Operator* CommonOperatorBuilder::ObjectState(int pointer_slots) {
+  return new (zone()) Operator1<int>(           // --
+      IrOpcode::kObjectState, Operator::kPure,  // opcode
+      "ObjectState",                            // name
+      pointer_slots, 0, 0, 1, 0, 0,             // counts
+      pointer_slots);                           // parameter
+}
+
+const Operator* CommonOperatorBuilder::TypedObjectState(
+    const ZoneVector<MachineType>* types) {
+  return new (zone()) Operator1<const ZoneVector<MachineType>*>(  // --
+      IrOpcode::kTypedObjectState, Operator::kPure,               // opcode
+      "TypedObjectState",                                         // name
+      static_cast<int>(types->size()), 0, 0, 1, 0, 0,             // counts
+      types);                                                     // parameter
+}
 
 const Operator* CommonOperatorBuilder::FrameState(
     BailoutId bailout_id, OutputFrameStateCombine state_combine,
diff --git a/src/compiler/common-operator.h b/src/compiler/common-operator.h
index 0fbf0bfda2..96f14f9d82 100644
--- a/src/compiler/common-operator.h
+++ b/src/compiler/common-operator.h
@@ -181,6 +181,9 @@ size_t hash_value(OsrGuardType type);
 std::ostream& operator<<(std::ostream&, OsrGuardType);
 OsrGuardType OsrGuardTypeOf(Operator const*);
 
+ZoneVector<MachineType> const* MachineTypesOf(Operator const*)
+    WARN_UNUSED_RESULT;
+
 // Interface for building common operators that can be used at any level of IR,
 // including JavaScript, mid-level, and low-level.
 class V8_EXPORT_PRIVATE CommonOperatorBuilder final
@@ -240,8 +243,9 @@ class V8_EXPORT_PRIVATE CommonOperatorBuilder final
   const Operator* BeginRegion(RegionObservability);
   const Operator* FinishRegion();
   const Operator* StateValues(int arguments);
-  const Operator* ObjectState(int pointer_slots, int id);
   const Operator* TypedStateValues(const ZoneVector<MachineType>* types);
+  const Operator* ObjectState(int pointer_slots);
+  const Operator* TypedObjectState(const ZoneVector<MachineType>* types);
   const Operator* FrameState(BailoutId bailout_id,
                              OutputFrameStateCombine state_combine,
                              const FrameStateFunctionInfo* function_info);
diff --git a/src/compiler/escape-analysis.cc b/src/compiler/escape-analysis.cc
index 7ce5e37e8c..f75a83b975 100644
--- a/src/compiler/escape-analysis.cc
+++ b/src/compiler/escape-analysis.cc
@@ -1578,8 +1578,8 @@ Node* EscapeAnalysis::GetOrCreateObjectState(Node* effect, Node* node) {
         }
         int input_count = static_cast<int>(cache_->fields().size());
         Node* new_object_state =
-            graph()->NewNode(common()->ObjectState(input_count, vobj->id()),
-                             input_count, &cache_->fields().front());
+            graph()->NewNode(common()->ObjectState(input_count), input_count,
+                             &cache_->fields().front());
         vobj->SetObjectState(new_object_state);
         TRACE(
             "Creating object state #%d for vobj %p (from node #%d) at effect "
diff --git a/src/compiler/instruction-selector.cc b/src/compiler/instruction-selector.cc
index 1889b6917f..2b48f7ded1 100644
--- a/src/compiler/instruction-selector.cc
+++ b/src/compiler/instruction-selector.cc
@@ -434,6 +434,7 @@ InstructionOperand OperandForDeopt(OperandGenerator* g, Node* input,
     case IrOpcode::kHeapConstant:
       return g->UseImmediate(input);
     case IrOpcode::kObjectState:
+    case IrOpcode::kTypedObjectState:
       UNREACHABLE();
       break;
     default:
@@ -485,6 +486,10 @@ size_t AddOperandToStateValueDescriptor(StateValueDescriptor* descriptor,
                                         FrameStateInputKind kind, Zone* zone) {
   switch (input->opcode()) {
     case IrOpcode::kObjectState: {
+      UNREACHABLE();
+      return 0;
+    }
+    case IrOpcode::kTypedObjectState: {
       size_t id = deduplicator->GetObjectId(input);
       if (id == StateObjectDeduplicator::kNotDuplicated) {
         size_t entries = 0;
@@ -492,10 +497,12 @@ size_t AddOperandToStateValueDescriptor(StateValueDescriptor* descriptor,
         descriptor->fields().push_back(
             StateValueDescriptor::Recursive(zone, id));
         StateValueDescriptor* new_desc = &descriptor->fields().back();
-        for (Edge edge : input->input_edges()) {
+        int const input_count = input->op()->ValueInputCount();
+        ZoneVector<MachineType> const* types = MachineTypesOf(input->op());
+        for (int i = 0; i < input_count; ++i) {
           entries += AddOperandToStateValueDescriptor(
-              new_desc, inputs, g, deduplicator, edge.to(),
-              MachineType::AnyTagged(), kind, zone);
+              new_desc, inputs, g, deduplicator, input->InputAt(i),
+              types->at(i), kind, zone);
         }
         return entries;
       } else {
@@ -506,7 +513,6 @@ size_t AddOperandToStateValueDescriptor(StateValueDescriptor* descriptor,
             StateValueDescriptor::Duplicate(zone, id));
         return 0;
       }
-      break;
     }
     default: {
       inputs->push_back(OperandForDeopt(g, input, kind, type.representation()));
diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h
index 8c9a11aa96..967bfb9d01 100644
--- a/src/compiler/opcodes.h
+++ b/src/compiler/opcodes.h
@@ -57,6 +57,7 @@
   V(StateValues)          \
   V(TypedStateValues)     \
   V(ObjectState)          \
+  V(TypedObjectState)     \
   V(Call)                 \
   V(Parameter)            \
   V(OsrValue)             \
diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc
index 3430db1b12..9555a3fe00 100644
--- a/src/compiler/simplified-lowering.cc
+++ b/src/compiler/simplified-lowering.cc
@@ -1012,6 +1012,36 @@ class RepresentationSelector {
     SetOutput(node, MachineRepresentation::kTagged);
   }
 
+  void VisitObjectState(Node* node) {
+    if (propagate()) {
+      for (int i = 0; i < node->InputCount(); i++) {
+        EnqueueInput(node, i, UseInfo::Any());
+      }
+    } else if (lower()) {
+      Zone* zone = jsgraph_->zone();
+      ZoneVector<MachineType>* types =
+          new (zone->New(sizeof(ZoneVector<MachineType>)))
+              ZoneVector<MachineType>(node->InputCount(), zone);
+      for (int i = 0; i < node->InputCount(); i++) {
+        Node* input = node->InputAt(i);
+        NodeInfo* input_info = GetInfo(input);
+        Type* input_type = TypeOf(input);
+        MachineRepresentation rep = input_type->IsInhabited()
+                                        ? input_info->representation()
+                                        : MachineRepresentation::kNone;
+        MachineType machine_type(rep, DeoptValueSemanticOf(input_type));
+        DCHECK(machine_type.representation() !=
+                   MachineRepresentation::kWord32 ||
+               machine_type.semantic() == MachineSemantic::kInt32 ||
+               machine_type.semantic() == MachineSemantic::kUint32);
+        (*types)[i] = machine_type;
+      }
+      NodeProperties::ChangeOp(node,
+                               jsgraph_->common()->TypedObjectState(types));
+    }
+    SetOutput(node, MachineRepresentation::kTagged);
+  }
+
   const Operator* Int32Op(Node* node) {
     return changer_->Int32OperatorFor(node->opcode());
   }
@@ -2456,6 +2486,8 @@ class RepresentationSelector {
         return;
       case IrOpcode::kStateValues:
         return VisitStateValues(node);
+      case IrOpcode::kObjectState:
+        return VisitObjectState(node);
       case IrOpcode::kTypeGuard: {
         // We just get rid of the sigma here. In principle, it should be
         // possible to refine the truncation and representation based on
@@ -2497,7 +2529,6 @@ class RepresentationSelector {
       case IrOpcode::kThrow:
       case IrOpcode::kBeginRegion:
       case IrOpcode::kProjection:
-      case IrOpcode::kObjectState:
       case IrOpcode::kOsrValue:
 // All JavaScript operators except JSToNumber have uniform handling.
 #define OPCODE_CASE(name) case IrOpcode::k##name:
diff --git a/src/compiler/state-values-utils.cc b/src/compiler/state-values-utils.cc
index 77cc227038..e8310d7d56 100644
--- a/src/compiler/state-values-utils.cc
+++ b/src/compiler/state-values-utils.cc
@@ -274,8 +274,7 @@ MachineType StateValuesAccess::iterator::type() {
     return MachineType::AnyTagged();
   } else {
     DCHECK_EQ(IrOpcode::kTypedStateValues, state->opcode());
-    const ZoneVector<MachineType>* types =
-        OpParameter<const ZoneVector<MachineType>*>(state);
+    ZoneVector<MachineType> const* types = MachineTypesOf(state->op());
     return (*types)[Top()->index];
   }
 }
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index 2f508d336e..7c2565cc42 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -820,12 +820,15 @@ Type* Typer::Visitor::TypeFrameState(Node* node) {
 
 Type* Typer::Visitor::TypeStateValues(Node* node) { return Type::Internal(); }
 
-Type* Typer::Visitor::TypeObjectState(Node* node) { return Type::Internal(); }
-
 Type* Typer::Visitor::TypeTypedStateValues(Node* node) {
   return Type::Internal();
 }
 
+Type* Typer::Visitor::TypeObjectState(Node* node) { return Type::Internal(); }
+
+Type* Typer::Visitor::TypeTypedObjectState(Node* node) {
+  return Type::Internal();
+}
 
 Type* Typer::Visitor::TypeCall(Node* node) { return Type::Any(); }
 
diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc
index 31153c6ef2..6552654cf3 100644
--- a/src/compiler/verifier.cc
+++ b/src/compiler/verifier.cc
@@ -488,8 +488,9 @@ void Verifier::Visitor::Check(Node* node) {
       break;
     }
     case IrOpcode::kStateValues:
-    case IrOpcode::kObjectState:
     case IrOpcode::kTypedStateValues:
+    case IrOpcode::kObjectState:
+    case IrOpcode::kTypedObjectState:
       // TODO(jarin): what are the constraints on these?
       break;
     case IrOpcode::kCall: