[torque] Introduce @abstract annotation for Torque classes

This annotation indicates that the class itself is not instantiated,
and does not have its own instance type: The instance types that
logically belong to the class are the instance types of the derived
classes.

Currently, we need the indication @dirtyInstantiatedAbstractClass
for several classes that are used as both, abstract base classes
and concrete classes. The prime example is JSObject which is the
base for many other classes, and also serves as the class to allocate
plain JSObjects. The annotation is purposefully ugly because in the
future we should refactor code to make it unnecessary.

Another annotation we introduce is @hasSameInstanceTypeAsParent,
which indicates another design pattern that currently occurs in the
code-base: Some Torque classes have the same instance types as their
parent class, but rename some fields, or possibly have a different map.
In such cases, the parent class is not abstract and the derived classes
can be seen as refinements of this class (that, for example, narrows the
type of a field). In the future, Torque should accomodate this pattern
better, but at moment we are content with just indicating where it is
used.

Bug: v8:7793
Change-Id: I1892dcc7325250df75d80308bf3d767d6d43bcc2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1607761
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61495}
diff --git a/src/builtins/base.tq b/src/builtins/base.tq
index b65d9a7..63b781a 100644
--- a/src/builtins/base.tq
+++ b/src/builtins/base.tq
@@ -30,7 +30,10 @@
 // The Smi value zero, which is often used as null for HeapObject types.
 type Zero extends PositiveSmi;
 
-extern class HeapObject extends Tagged { map: Map; }
+@abstract
+extern class HeapObject extends Tagged {
+  map: Map;
+}
 
 type Object = Smi | HeapObject;
 type int32 generates 'TNode<Int32T>' constexpr 'int32_t';
@@ -78,6 +81,7 @@
 type BigInt extends HeapObject generates 'TNode<BigInt>';
 type Numeric = Number | BigInt;
 
+@abstract
 @noVerifier
 extern class Name extends HeapObject {
   hash_field: int32;
@@ -88,14 +92,17 @@
   name: Object;
 }
 
-// abstract
-extern class String extends Name { length: uint32; }
+@abstract
+extern class String extends Name {
+  length: uint32;
+}
 
 extern class ConsString extends String {
   first: String;
   second: String;
 }
 
+@abstract
 @noVerifier
 extern class ExternalString extends String {
   resource: RawPtr;
@@ -108,6 +115,7 @@
 extern class InternalizedString extends String {}
 
 // TODO(v8:8983): Add declaration for variable-sized region.
+@abstract
 @noVerifier
 extern class SeqString extends String {
 }
@@ -124,16 +132,21 @@
 // The HeapNumber value NaN
 type NaN extends HeapNumber;
 
+@abstract
 @noVerifier
 extern class Struct extends HeapObject {
 }
 
+@abstract
+@dirtyInstantiatedAbstractClass
 @generatePrint
 extern class Tuple2 extends Struct {
   value1: Object;
   value2: Object;
 }
 
+@abstract
+@dirtyInstantiatedAbstractClass
 @generatePrint
 extern class Tuple3 extends Tuple2 {
   value3: Object;
@@ -147,6 +160,7 @@
 
 type Map extends HeapObject generates 'TNode<Map>';
 
+@abstract
 @noVerifier
 extern class FixedArrayBase extends HeapObject {
   length: Smi;
@@ -191,6 +205,7 @@
 intrinsic %Allocate<Class: type>(size: intptr): Class;
 intrinsic %AllocateInternalClass<Class: type>(slotCount: constexpr intptr): Class;
 
+@abstract
 @noVerifier
 extern class JSReceiver extends HeapObject {
   properties_or_hash: FixedArrayBase | Smi;
@@ -198,6 +213,8 @@
 
 type Constructor extends JSReceiver;
 
+@abstract
+@dirtyInstantiatedAbstractClass
 extern class JSObject extends JSReceiver {
   @noVerifier elements: FixedArrayBase;
 }
@@ -254,9 +271,11 @@
 
 extern class JSArgumentsObject extends JSObject {}
 @noVerifier
+@hasSameInstanceTypeAsParent
 extern class JSArgumentsObjectWithLength extends JSArgumentsObject {
   length: Object;
 }
+@hasSameInstanceTypeAsParent
 extern class JSSloppyArgumentsObject extends JSArgumentsObjectWithLength {
   callee: Object;
 }
@@ -327,6 +346,7 @@
 type JSModuleNamespace extends JSObject;
 type WeakArrayList extends HeapObject;
 
+@abstract
 @noVerifier
 extern class JSWeakCollection extends JSObject {
   table: Object;
@@ -431,7 +451,7 @@
 type FixedTypedArray extends FixedTypedArrayBase
     generates 'TNode<FixedTypedArray>';
 
-extern class SloppyArgumentsElements extends FixedArray {}
+type SloppyArgumentsElements extends FixedArray;
 type NumberDictionary extends HeapObject
     generates 'TNode<NumberDictionary>';
 
@@ -491,6 +511,7 @@
   backing_store: RawPtr;
 }
 
+@abstract
 extern class JSArrayBufferView extends JSObject {
   buffer: JSArrayBuffer;
   byte_offset: uintptr;
@@ -537,6 +558,7 @@
   configurable: Object;
 }
 
+@abstract
 @noVerifier
 extern class JSCollection extends JSObject {
   table: Object;
@@ -579,6 +601,7 @@
   configurable: Object;
 }
 
+@abstract
 extern class TemplateInfo extends Struct {
   tag: Object;
   serial_number: Object;
@@ -616,7 +639,7 @@
 
 extern class PropertyArray extends HeapObject { length_and_hash: Smi; }
 
-extern class DependentCode extends WeakFixedArray {}
+type DependentCode extends WeakFixedArray;
 
 extern class PropertyCell extends HeapObject {
   name: Name;
@@ -686,6 +709,8 @@
   @noVerifier weak data_3: Object;
 }
 
+@abstract
+@dirtyInstantiatedAbstractClass
 extern class JSGeneratorObject extends JSObject {
   function: JSFunction;
   context: Context;
@@ -710,7 +735,9 @@
   flags: Smi;
 }
 
-extern class Microtask extends Struct {}
+@abstract
+extern class Microtask extends Struct {
+}
 
 extern class CallbackTask extends Microtask {
   callback: Foreign;
@@ -1005,6 +1032,7 @@
   promise_or_capability: JSPromise | PromiseCapability | Undefined;
 }
 
+@abstract
 extern class PromiseReactionJobTask extends Microtask {
   argument: Object;
   context: Context;
@@ -1061,6 +1089,7 @@
 extern operator '.lastIndex=' macro
 RegExpBuiltinsAssembler::FastStoreLastIndex(FastJSRegExp, Smi): void;
 
+@hasSameInstanceTypeAsParent
 extern class JSRegExpResult extends JSArray {
   index: Object;
   input: Object;
@@ -1082,6 +1111,7 @@
   return kRegExpMatchInfoFirstCaptureIndex + (captureIndex * 2);
 }
 
+@hasSameInstanceTypeAsParent
 extern class RegExpMatchInfo extends FixedArray {
   GetStartOfCapture(implicit context: Context)(captureIndex: constexpr int31):
       Smi {
@@ -1115,7 +1145,7 @@
 
 extern class BreakPoint extends Tuple2 {}
 extern class BreakPointInfo extends Tuple2 {}
-extern class CoverageInfo extends FixedArray {}
+type CoverageInfo extends FixedArray;
 
 extern class DebugInfo extends Struct {
   shared_function_info: SharedFunctionInfo;
@@ -1187,9 +1217,7 @@
 extern class WasmExceptionPackage extends JSReceiver {
 }
 
-@noVerifier
-extern class WasmExportedFunction extends JSFunction {
-}
+type WasmExportedFunction extends JSFunction;
 
 extern class AsmWasmData extends Struct {
   managed_native_module: Foreign;  // Managed<wasm::NativeModule>
diff --git a/src/builtins/builtins-promise-gen.cc b/src/builtins/builtins-promise-gen.cc
index aa55671..35bf705 100644
--- a/src/builtins/builtins-promise-gen.cc
+++ b/src/builtins/builtins-promise-gen.cc
@@ -516,7 +516,8 @@
 Node* PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask(
     Node* map, Node* context, Node* argument, Node* handler,
     Node* promise_or_capability) {
-  Node* const microtask = Allocate(PromiseReactionJobTask::kSize);
+  Node* const microtask =
+      Allocate(PromiseReactionJobTask::kSizeOfAllPromiseReactionJobTasks);
   StoreMapNoWriteBarrier(microtask, map);
   StoreObjectFieldNoWriteBarrier(
       microtask, PromiseReactionJobTask::kArgumentOffset, argument);
@@ -640,8 +641,10 @@
       // Morph {current} from a PromiseReaction into a PromiseReactionJobTask
       // and schedule that on the microtask queue. We try to minimize the number
       // of stores here to avoid screwing up the store buffer.
-      STATIC_ASSERT(static_cast<int>(PromiseReaction::kSize) ==
-                    static_cast<int>(PromiseReactionJobTask::kSize));
+      STATIC_ASSERT(
+          static_cast<int>(PromiseReaction::kSize) ==
+          static_cast<int>(
+              PromiseReactionJobTask::kSizeOfAllPromiseReactionJobTasks));
       if (type == PromiseReaction::kFulfill) {
         StoreMapNoWriteBarrier(current,
                                RootIndex::kPromiseFulfillReactionJobTaskMap);
diff --git a/src/objects-body-descriptors-inl.h b/src/objects-body-descriptors-inl.h
index 503aaeb..8cba562 100644
--- a/src/objects-body-descriptors-inl.h
+++ b/src/objects-body-descriptors-inl.h
@@ -553,7 +553,7 @@
 
 class JSWeakCollection::BodyDescriptorImpl final : public BodyDescriptorBase {
  public:
-  STATIC_ASSERT(kTableOffset + kTaggedSize == kSize);
+  STATIC_ASSERT(kTableOffset + kTaggedSize == kSizeOfAllWeakCollections);
 
   static bool IsValidSlot(Map map, HeapObject obj, int offset) {
     return IsValidJSObjectSlotImpl(map, obj, offset);
diff --git a/src/objects-debug.cc b/src/objects-debug.cc
index 32fe672..b0a2a6f 100644
--- a/src/objects-debug.cc
+++ b/src/objects-debug.cc
@@ -866,7 +866,7 @@
 
 void SloppyArgumentsElements::SloppyArgumentsElementsVerify(Isolate* isolate,
                                                             JSObject holder) {
-  TorqueGeneratedClassVerifiers::SloppyArgumentsElementsVerify(*this, isolate);
+  FixedArrayVerify(isolate);
   // Abort verification if only partially initialized (can't use arguments()
   // getter because it does FixedArray::cast()).
   if (get(kArgumentsIndex)->IsUndefined(isolate)) return;
diff --git a/src/objects.cc b/src/objects.cc
index 634f8e2..70b6966 100644
--- a/src/objects.cc
+++ b/src/objects.cc
@@ -6122,8 +6122,10 @@
     }
     if (handler_context.is_null()) handler_context = isolate->native_context();
 
-    STATIC_ASSERT(static_cast<int>(PromiseReaction::kSize) ==
-                  static_cast<int>(PromiseReactionJobTask::kSize));
+    STATIC_ASSERT(
+        static_cast<int>(PromiseReaction::kSize) ==
+        static_cast<int>(
+            PromiseReactionJobTask::kSizeOfAllPromiseReactionJobTasks));
     if (type == PromiseReaction::kFulfill) {
       task->synchronized_set_map(
           ReadOnlyRoots(isolate).promise_fulfill_reaction_job_task_map());
diff --git a/src/objects/fixed-array.h b/src/objects/fixed-array.h
index e71d257..f20efb5 100644
--- a/src/objects/fixed-array.h
+++ b/src/objects/fixed-array.h
@@ -100,8 +100,6 @@
   DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize,
                                 TORQUE_GENERATED_FIXED_ARRAY_BASE_FIELDS)
 
-  static const int kHeaderSize = kSize;
-
  protected:
   // Special-purpose constructor for subclasses that have fast paths where
   // their ptr() is a Smi.
diff --git a/src/objects/js-collection.h b/src/objects/js-collection.h
index 0450de8..4fd5375 100644
--- a/src/objects/js-collection.h
+++ b/src/objects/js-collection.h
@@ -44,6 +44,8 @@
   // Dispatched behavior.
   DECL_PRINTER(JSSet)
   DECL_VERIFIER(JSSet)
+  DEFINE_FIELD_OFFSET_CONSTANTS(JSCollection::kHeaderSize,
+                                TORQUE_GENERATED_JSWEAK_SET_FIELDS)
 
   OBJECT_CONSTRUCTORS(JSSet, JSCollection);
 };
@@ -72,6 +74,8 @@
   // Dispatched behavior.
   DECL_PRINTER(JSMap)
   DECL_VERIFIER(JSMap)
+  DEFINE_FIELD_OFFSET_CONSTANTS(JSCollection::kHeaderSize,
+                                TORQUE_GENERATED_JSWEAK_MAP_FIELDS)
 
   OBJECT_CONSTRUCTORS(JSMap, JSCollection);
 };
@@ -121,6 +125,8 @@
   // Visit the whole object.
   using BodyDescriptor = BodyDescriptorImpl;
 
+  static const int kSizeOfAllWeakCollections = kHeaderSize;
+
   OBJECT_CONSTRUCTORS(JSWeakCollection, JSObject);
 };
 
@@ -133,6 +139,9 @@
   DECL_PRINTER(JSWeakMap)
   DECL_VERIFIER(JSWeakMap)
 
+  DEFINE_FIELD_OFFSET_CONSTANTS(JSWeakCollection::kHeaderSize,
+                                TORQUE_GENERATED_JSWEAK_MAP_FIELDS)
+  STATIC_ASSERT(kSize == kSizeOfAllWeakCollections);
   OBJECT_CONSTRUCTORS(JSWeakMap, JSWeakCollection);
 };
 
@@ -144,6 +153,9 @@
   // Dispatched behavior.
   DECL_PRINTER(JSWeakSet)
   DECL_VERIFIER(JSWeakSet)
+  DEFINE_FIELD_OFFSET_CONSTANTS(JSWeakCollection::kHeaderSize,
+                                TORQUE_GENERATED_JSWEAK_SET_FIELDS)
+  STATIC_ASSERT(kSize == kSizeOfAllWeakCollections);
 
   OBJECT_CONSTRUCTORS(JSWeakSet, JSWeakCollection);
 };
diff --git a/src/objects/js-objects.h b/src/objects/js-objects.h
index e029648..137e3a0 100644
--- a/src/objects/js-objects.h
+++ b/src/objects/js-objects.h
@@ -266,8 +266,6 @@
 
   DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize,
                                 TORQUE_GENERATED_JSRECEIVER_FIELDS)
-  static const int kHeaderSize = kSize;
-
   bool HasProxyInPrototype(Isolate* isolate);
 
   bool HasComplexElements();
diff --git a/src/objects/name.h b/src/objects/name.h
index d131351..aa8d099 100644
--- a/src/objects/name.h
+++ b/src/objects/name.h
@@ -71,8 +71,6 @@
   DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize,
                                 TORQUE_GENERATED_NAME_FIELDS)
 
-  static const int kHeaderSize = kSize;
-
   // Mask constant for checking if a name has a computed hash code
   // and if it is a string that is an array index.  The least significant bit
   // indicates whether a hash code has been computed.  If the hash code has
diff --git a/src/objects/promise.h b/src/objects/promise.h
index 0b15546..f7c6041 100644
--- a/src/objects/promise.h
+++ b/src/objects/promise.h
@@ -39,7 +39,7 @@
   // Dispatched behavior.
   DECL_CAST(PromiseReactionJobTask)
   DECL_VERIFIER(PromiseReactionJobTask)
-
+  static const int kSizeOfAllPromiseReactionJobTasks = kHeaderSize;
   OBJECT_CONSTRUCTORS(PromiseReactionJobTask, Microtask);
 };
 
@@ -51,6 +51,11 @@
   DECL_PRINTER(PromiseFulfillReactionJobTask)
   DECL_VERIFIER(PromiseFulfillReactionJobTask)
 
+  DEFINE_FIELD_OFFSET_CONSTANTS(
+      PromiseReactionJobTask::kHeaderSize,
+      TORQUE_GENERATED_PROMISE_FULFILL_REACTION_JOB_TASK_FIELDS)
+  STATIC_ASSERT(kSize == kSizeOfAllPromiseReactionJobTasks);
+
   OBJECT_CONSTRUCTORS(PromiseFulfillReactionJobTask, PromiseReactionJobTask);
 };
 
@@ -62,6 +67,11 @@
   DECL_PRINTER(PromiseRejectReactionJobTask)
   DECL_VERIFIER(PromiseRejectReactionJobTask)
 
+  DEFINE_FIELD_OFFSET_CONSTANTS(
+      PromiseReactionJobTask::kHeaderSize,
+      TORQUE_GENERATED_PROMISE_REJECT_REACTION_JOB_TASK_FIELDS)
+  STATIC_ASSERT(kSize == kSizeOfAllPromiseReactionJobTasks);
+
   OBJECT_CONSTRUCTORS(PromiseRejectReactionJobTask, PromiseReactionJobTask);
 };
 
diff --git a/src/objects/string.cc b/src/objects/string.cc
index 5c7c7fd..f1fafa0 100644
--- a/src/objects/string.cc
+++ b/src/objects/string.cc
@@ -164,7 +164,7 @@
   // strings in generated code, we need to bailout to runtime.
   Map new_map;
   ReadOnlyRoots roots(heap);
-  if (size < ExternalString::kSize) {
+  if (size < ExternalString::kSizeOfAllExternalStrings) {
     if (is_internalized) {
       new_map = roots.uncached_external_internalized_string_map();
     } else {
@@ -238,7 +238,7 @@
   // strings in generated code, we need to bailout to runtime.
   Map new_map;
   ReadOnlyRoots roots(heap);
-  if (size < ExternalString::kSize) {
+  if (size < ExternalString::kSizeOfAllExternalStrings) {
     new_map = is_internalized
                   ? roots.uncached_external_one_byte_internalized_string_map()
                   : roots.uncached_external_one_byte_string_map();
diff --git a/src/objects/string.h b/src/objects/string.h
index a494a8a..55e0a36 100644
--- a/src/objects/string.h
+++ b/src/objects/string.h
@@ -341,8 +341,6 @@
   DEFINE_FIELD_OFFSET_CONSTANTS(Name::kHeaderSize,
                                 TORQUE_GENERATED_STRING_FIELDS)
 
-  static const int kHeaderSize = kSize;
-
   // Max char codes.
   static const int32_t kMaxOneByteCharCode = unibrow::Latin1::kMaxChar;
   static const uint32_t kMaxOneByteCharCodeU = unibrow::Latin1::kMaxChar;
@@ -698,6 +696,7 @@
   inline void DisposeResource();
 
   STATIC_ASSERT(kResourceOffset == Internals::kStringResourceOffset);
+  static const int kSizeOfAllExternalStrings = kHeaderSize;
 
   OBJECT_CONSTRUCTORS(ExternalString, String);
 };
@@ -734,6 +733,12 @@
 
   class BodyDescriptor;
 
+  DEFINE_FIELD_OFFSET_CONSTANTS(
+      ExternalString::kHeaderSize,
+      TORQUE_GENERATED_EXTERNAL_ONE_BYTE_STRING_FIELDS)
+
+  STATIC_ASSERT(kSize == kSizeOfAllExternalStrings);
+
   OBJECT_CONSTRUCTORS(ExternalOneByteString, ExternalString);
 };
 
@@ -772,6 +777,12 @@
 
   class BodyDescriptor;
 
+  DEFINE_FIELD_OFFSET_CONSTANTS(
+      ExternalString::kHeaderSize,
+      TORQUE_GENERATED_EXTERNAL_TWO_BYTE_STRING_FIELDS)
+
+  STATIC_ASSERT(kSize == kSizeOfAllExternalStrings);
+
   OBJECT_CONSTRUCTORS(ExternalTwoByteString, ExternalString);
 };
 
diff --git a/src/objects/templates.h b/src/objects/templates.h
index bd55821..daa257d 100644
--- a/src/objects/templates.h
+++ b/src/objects/templates.h
@@ -169,7 +169,7 @@
 
   static const int kInvalidSerialNumber = 0;
 
-  DEFINE_FIELD_OFFSET_CONSTANTS(TemplateInfo::kSize,
+  DEFINE_FIELD_OFFSET_CONSTANTS(TemplateInfo::kHeaderSize,
                                 TORQUE_GENERATED_FUNCTION_TEMPLATE_INFO_FIELDS)
 
   static Handle<SharedFunctionInfo> GetOrCreateSharedFunctionInfo(
@@ -221,7 +221,7 @@
   DECL_VERIFIER(ObjectTemplateInfo)
 
   // Layout description.
-  DEFINE_FIELD_OFFSET_CONSTANTS(TemplateInfo::kSize,
+  DEFINE_FIELD_OFFSET_CONSTANTS(TemplateInfo::kHeaderSize,
                                 TORQUE_GENERATED_OBJECT_TEMPLATE_INFO_FIELDS)
 
   // Starting from given object template's constructor walk up the inheritance
diff --git a/src/torque/constants.h b/src/torque/constants.h
index da41f2a..9998702 100644
--- a/src/torque/constants.h
+++ b/src/torque/constants.h
@@ -67,7 +67,10 @@
   kGeneratePrint = 1 << 1,
   kGenerateVerify = 1 << 2,
   kTransient = 1 << 3,
-  kHasIndexedField = 1 << 4
+  kAbstract = 1 << 4,
+  kInstantiatedAbstractClass = 1 << 5,
+  kHasSameInstanceTypeAsParent = 1 << 6,
+  kHasIndexedField = 1 << 7
 };
 using ClassFlags = base::Flags<ClassFlag>;
 
diff --git a/src/torque/implementation-visitor.cc b/src/torque/implementation-visitor.cc
index a7d1755..4d92ce51 100644
--- a/src/torque/implementation-visitor.cc
+++ b/src/torque/implementation-visitor.cc
@@ -164,13 +164,15 @@
     // TODO(danno): This is a pretty cheesy hack for now. There should be a more
     // robust mechanism for this, e.g. declaring classes 'extern' or something.
     if (class_type->nspace()->IsTestNamespace()) {
-      std::string class_name{
-          class_type->GetSuperClass()->GetGeneratedTNodeTypeName()};
+      const ClassType* super = class_type->GetSuperClass();
+      std::string class_name{super->GetGeneratedTNodeTypeName()};
       header_out() << "  class " << class_type->name() << " : public "
                    << class_name << " {\n";
       header_out() << "   public:\n";
       header_out() << "    DEFINE_FIELD_OFFSET_CONSTANTS(" << class_name
-                   << "::kSize, TORQUE_GENERATED_"
+                   << "::";
+      header_out() << (super->IsAbstract() ? "kHeaderSize" : "kSize");
+      header_out() << ", TORQUE_GENERATED_"
                    << CapifyStringWithUnderscores(class_type->name())
                    << "_FIELDS)\n";
       header_out() << "  };\n";
@@ -2954,7 +2956,6 @@
         new_contents_stream << "V(k" << CamelifyString(f.name_and_type.name)
                             << "Offset, " << size_string << ") \\\n";
       }
-
       ProcessFieldInSection(&section, &completed_sections,
                             FieldSectionType::kNoSection, &new_contents_stream);
       CompleteFieldSection(&section, &completed_sections,
@@ -2964,7 +2965,12 @@
                            FieldSectionType::kStrongSection,
                            &new_contents_stream);
 
-      new_contents_stream << "V(kSize, 0) \\\n";
+      if (type->IsAbstract()) {
+        new_contents_stream << "V(kHeaderSize, 0) \\\n";
+      }
+      if (!type->IsAbstract() || type->IsInstantiatedAbstractClass()) {
+        new_contents_stream << "V(kSize, 0) \\\n";
+      }
       new_contents_stream << "\n";
     }
   }
diff --git a/src/torque/torque-parser.cc b/src/torque/torque-parser.cc
index 0dc9532..10995ee 100644
--- a/src/torque/torque-parser.cc
+++ b/src/torque/torque-parser.cc
@@ -656,13 +656,24 @@
 
 base::Optional<ParseResult> MakeClassDeclaration(
     ParseResultIterator* child_results) {
-  AnnotationSet annotations(child_results,
-                            {"@generatePrint", "@noVerifier"});
+  AnnotationSet annotations(
+      child_results,
+      {"@generatePrint", "@noVerifier", "@abstract",
+       "@dirtyInstantiatedAbstractClass", "@hasSameInstanceTypeAsParent"});
   ClassFlags flags = ClassFlag::kNone;
   bool generate_print = annotations.Contains("@generatePrint");
   if (generate_print) flags |= ClassFlag::kGeneratePrint;
   bool generate_verify = !annotations.Contains("@noVerifier");
   if (generate_verify) flags |= ClassFlag::kGenerateVerify;
+  if (annotations.Contains("@abstract")) {
+    flags |= ClassFlag::kAbstract;
+  }
+  if (annotations.Contains("@dirtyInstantiatedAbstractClass")) {
+    flags |= ClassFlag::kInstantiatedAbstractClass;
+  }
+  if (annotations.Contains("@hasSameInstanceTypeAsParent")) {
+    flags |= ClassFlag::kHasSameInstanceTypeAsParent;
+  }
   auto is_extern = child_results->NextAs<bool>();
   if (is_extern) flags |= ClassFlag::kExtern;
   auto transient = child_results->NextAs<bool>();
diff --git a/src/torque/types.cc b/src/torque/types.cc
index 0619191..400de51 100644
--- a/src/torque/types.cc
+++ b/src/torque/types.cc
@@ -329,7 +329,8 @@
 }
 
 bool ClassType::AllowInstantiation() const {
-  return !IsExtern() || nspace()->IsDefaultNamespace();
+  return (!IsExtern() || nspace()->IsDefaultNamespace()) &&
+         (!IsAbstract() || IsInstantiatedAbstractClass());
 }
 
 void ClassType::Finalize() const {
@@ -339,6 +340,14 @@
   if (parent()) {
     if (const ClassType* super_class = ClassType::DynamicCast(parent())) {
       if (super_class->HasIndexedField()) flags_ |= ClassFlag::kHasIndexedField;
+      if (!super_class->IsAbstract() && !HasSameInstanceTypeAsParent()) {
+        ReportLintError(
+            "Super class must either be abstract (annotate super class with "
+            "@abstract) "
+            "or this class must have the same instance type as the super class "
+            "(annotate this class with @hasSameInstanceTypeAsParent).",
+            this->decl_->name->pos);
+      }
     }
   }
   TypeVisitor::VisitClassFieldsAndMethods(const_cast<ClassType*>(this),
diff --git a/src/torque/types.h b/src/torque/types.h
index 2de03bb..5e30201 100644
--- a/src/torque/types.h
+++ b/src/torque/types.h
@@ -517,6 +517,13 @@
     return flags_ & ClassFlag::kGenerateVerify;
   }
   bool IsTransient() const override { return flags_ & ClassFlag::kTransient; }
+  bool IsAbstract() const { return flags_ & ClassFlag::kAbstract; }
+  bool IsInstantiatedAbstractClass() const {
+    return flags_ & ClassFlag::kInstantiatedAbstractClass;
+  }
+  bool HasSameInstanceTypeAsParent() const {
+    return flags_ & ClassFlag::kHasSameInstanceTypeAsParent;
+  }
   bool HasIndexedField() const override;
   size_t size() const { return size_; }
   const ClassType* GetSuperClass() const {
diff --git a/test/torque/test-torque.tq b/test/torque/test-torque.tq
index cafca7a..f0cdfc8 100644
--- a/test/torque/test-torque.tq
+++ b/test/torque/test-torque.tq
@@ -896,6 +896,7 @@
 
   type Baztype = Foo | FooType;
 
+  @abstract
   @noVerifier
   extern class Foo extends JSObject {
     fooField: FooType;