[vm,bytecode] Fix canonicalization of type arguments of recursive types

Previously, it was possible that type arguments of recursive types are
shared between unrelated recursive types if they have exactly the same shape
(e.g. Foo extends Base<Foo> and Bar extends Base<Bar> both have
type arguments <recursive-ref#0>).

Fixes ffi/structs_test in AOT+bytecode mode.

Change-Id: I516bc34c3b5797d76e1bf087ac3c8f075f1b6424
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/120380
Reviewed-by: RĂ©gis Crelier <regis@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/lib/bytecode/object_table.dart b/pkg/vm/lib/bytecode/object_table.dart
index 4170bbb..5eb9122 100644
--- a/pkg/vm/lib/bytecode/object_table.dart
+++ b/pkg/vm/lib/bytecode/object_table.dart
@@ -1161,6 +1161,16 @@
   ObjectKind get kind => ObjectKind.kTypeArguments;
 
   @override
+  bool get isCacheable {
+    for (var arg in args) {
+      if (!arg.isCacheable) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @override
   void writeContents(BufferedWriter writer) {
     writer.writePackedList(args);
   }
diff --git a/pkg/vm/test/bytecode/object_table_test.dart b/pkg/vm/test/bytecode/object_table_test.dart
new file mode 100644
index 0000000..75ad973
--- /dev/null
+++ b/pkg/vm/test/bytecode/object_table_test.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/core_types.dart' show CoreTypes;
+import 'package:kernel/testing/mock_sdk_component.dart';
+import 'package:test/test.dart';
+import 'package:expect/expect.dart';
+import 'package:vm/bytecode/object_table.dart';
+
+main() {
+  Library lib1;
+  Library lib2;
+  CoreTypes coreTypes;
+  Supertype objectSupertype;
+  ObjectTable objectTable;
+
+  Class addClass(Library lib, String name,
+      [List<TypeParameter> typeParameters = const []]) {
+    Class cls = new Class(
+        name: name, supertype: objectSupertype, typeParameters: typeParameters);
+    cls.parent = lib;
+    lib.addClass(cls);
+    return cls;
+  }
+
+  setUp(() {
+    // Start with mock SDK libraries.
+    Component component = createMockSdkComponent();
+    coreTypes = new CoreTypes(component);
+    objectSupertype = coreTypes.objectClass.asThisSupertype;
+
+    // Add test libraries.
+    lib1 = new Library(Uri.parse('org-dartlang:///test1.dart'), name: 'lib1');
+    lib1.parent = component;
+    component.libraries.add(lib1);
+
+    lib2 = new Library(Uri.parse('org-dartlang:///test2.dart'), name: 'lib2');
+    lib2.parent = component;
+    component.libraries.add(lib2);
+
+    objectTable = new ObjectTable();
+  });
+
+  tearDown(() {});
+
+  test('libraries', () {
+    final h1 = objectTable.getHandle(lib1);
+    final h2 = objectTable.getHandle(lib2);
+    Expect.notEquals(h1, h2);
+    Expect.identical(h1, objectTable.getHandle(lib1));
+    Expect.identical(h2, objectTable.getHandle(lib2));
+    Expect.equals(true, h1.isCacheable);
+    Expect.equals(true, h2.isCacheable);
+    Expect.equals(false, h1.shouldBeIncludedIntoIndexTable); // 2 uses
+    Expect.identical(h1, objectTable.getHandle(lib1));
+    Expect.equals(true, h1.shouldBeIncludedIntoIndexTable); // 3 uses
+  });
+
+  test('classes', () {
+    final Class c1 = addClass(lib1, 'A');
+    final Class c2 = addClass(lib1, 'B');
+    final Class c3 = addClass(lib2, 'A');
+    final h1 = objectTable.getHandle(c1);
+    final h2 = objectTable.getHandle(c2);
+    final h3 = objectTable.getHandle(c3);
+    Expect.notEquals(h1, h2);
+    Expect.notEquals(h1, h3);
+    Expect.notEquals(h2, h3);
+    Expect.identical(h1, objectTable.getHandle(c1));
+    Expect.identical(h2, objectTable.getHandle(c2));
+    Expect.identical(h3, objectTable.getHandle(c3));
+    Expect.equals(true, h1.isCacheable);
+    Expect.equals(true, h2.isCacheable);
+    Expect.equals(true, h3.isCacheable);
+    final lib1h = objectTable.getHandle(lib1);
+    final lib2h = objectTable.getHandle(lib2);
+    Expect.equals(true, lib1h.shouldBeIncludedIntoIndexTable); // 3 uses
+    Expect.equals(false, lib2h.shouldBeIncludedIntoIndexTable); // 2 uses
+  });
+
+  test('simple-types', () {
+    final h1a = objectTable.getHandle(new InterfaceType(coreTypes.intClass));
+    final h1b = objectTable.getHandle(new InterfaceType(coreTypes.intClass));
+    final h2a = objectTable.getHandle(const DynamicType());
+    final h2b = objectTable.getHandle(new DynamicType());
+    Expect.identical(h1a, h1b);
+    Expect.identical(h2a, h2b);
+    Expect.notEquals(h1a, h2a);
+    Expect.equals(true, h1a.isCacheable);
+    Expect.equals(true, h2a.isCacheable);
+    Expect.equals(false, h1a.shouldBeIncludedIntoIndexTable); // 2 uses
+    objectTable.getHandle(new InterfaceType(
+        coreTypes.listClass, [new InterfaceType(coreTypes.intClass)]));
+    Expect.equals(true, h1a.shouldBeIncludedIntoIndexTable); // 3 uses
+  });
+
+  test('recursive-types', () {
+    final base = addClass(lib1, "Base", [new TypeParameter("T")]);
+    final derived1 = addClass(lib1, "Derived");
+    derived1.supertype = new Supertype(base, [new InterfaceType(derived1)]);
+    final derived2 = addClass(lib2, "Derived");
+    derived2.supertype = new Supertype(base, [new InterfaceType(derived2)]);
+    final h1a = objectTable.getHandle(new InterfaceType(derived1));
+    final h1b = objectTable.getHandle(new InterfaceType(derived1));
+    final h2a = objectTable.getHandle(new InterfaceType(derived2));
+    final h2b = objectTable.getHandle(new InterfaceType(derived2));
+    Expect.identical(h1a, h1b);
+    Expect.identical(h2a, h2b);
+    Expect.notEquals(h1a, h2a);
+    Expect.equals(true, h1a.isCacheable);
+    Expect.equals(true, h2a.isCacheable);
+    final typeArgs1 = (h1a as dynamic).typeArgs;
+    final typeArgs2 = (h2a as dynamic).typeArgs;
+    Expect.equals(false, typeArgs1.isCacheable);
+    Expect.equals(false, typeArgs2.isCacheable);
+  });
+}