[dart2js] new-rti: bindings

HInstanceEnvironment returns the type of an instance, which gives access to all
type variables of the supertypes. HInstanceEnvironment is currently lowered to a
call to 'instanceType', but we expect to lower to a field access in
most cases, or a constant type which can be folded into subsequent operations.

The type_builder builds a type environment for each type expression. The
environment is build from the instance type scope and the function type scopes.
GVN optimizations already introduce more sharing, but it might be advantageous to
pre-compute the environment and sink the operations instead.
Later we will pass multiple type arguments as tuples which will reduce the number
of operations, especially in factory constructors.

There are some tweaks removing types in rti.dart to avoid re-entrant code.


Change-Id: I004adf7a8a464e575b32d59476c888db46542fde
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107460
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index 2778f65..b9c1803 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -481,6 +481,7 @@
   // From dart:_rti
 
   FunctionEntity get findType;
+  FunctionEntity get instanceType;
   FieldEntity get rtiAsField;
   FieldEntity get rtiCheckField;
   FieldEntity get rtiIsField;
@@ -1811,6 +1812,11 @@
   @override
   FunctionEntity get findType => _findType ??= _findRtiFunction('findType');
 
+  FunctionEntity _instanceType;
+  @override
+  FunctionEntity get instanceType =>
+      _instanceType ??= _findRtiFunction('instanceType');
+
   ClassEntity get _rtiImplClass => _findClass(rtiLibrary, 'Rti');
   FieldEntity _findRtiClassField(String name) =>
       _findClassMember(_rtiImplClass, name);
diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart
index d80aa0a..3edb017 100644
--- a/pkg/compiler/lib/src/js_backend/backend_impact.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart
@@ -769,6 +769,7 @@
     return [
       BackendImpact(staticUses: [
         _commonElements.findType,
+        _commonElements.instanceType,
         _commonElements.rtiEvalMethod,
         _commonElements.rtiBindMethod,
       ])
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 1c0a232..e23dcf8 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -789,25 +789,31 @@
       bool needsTypeArguments =
           closedWorld.rtiNeed.classNeedsTypeArguments(cls);
       if (needsTypeArguments) {
-        // Read the values of the type arguments and create a
-        // HTypeInfoExpression to set on the newly created object.
-        List<HInstruction> typeArguments = <HInstruction>[];
-        InterfaceType thisType = _elementEnvironment.getThisType(cls);
-        for (DartType typeVariable in thisType.typeArguments) {
-          HInstruction argument = localsHandler
-              .readLocal(localsHandler.getTypeVariableAsLocal(typeVariable));
-          typeArguments.add(argument);
+        if (options.experimentNewRti) {
+          InterfaceType thisType = _elementEnvironment.getThisType(cls);
+          HInstruction typeArgument =
+              _typeBuilder.analyzeTypeArgumentNewRti(thisType, sourceElement);
+          constructorArguments.add(typeArgument);
+        } else {
+          // Read the values of the type arguments and create a
+          // HTypeInfoExpression to set on the newly created object.
+          List<HInstruction> typeArguments = <HInstruction>[];
+          InterfaceType thisType = _elementEnvironment.getThisType(cls);
+          for (DartType typeVariable in thisType.typeArguments) {
+            HInstruction argument = localsHandler
+                .readLocal(localsHandler.getTypeVariableAsLocal(typeVariable));
+            typeArguments.add(argument);
+          }
+
+          HInstruction typeInfo = new HTypeInfoExpression(
+              TypeInfoExpressionKind.INSTANCE,
+              thisType,
+              typeArguments,
+              _abstractValueDomain.dynamicType);
+          add(typeInfo);
+          constructorArguments.add(typeInfo);
         }
-
-        HInstruction typeInfo = new HTypeInfoExpression(
-            TypeInfoExpressionKind.INSTANCE,
-            thisType,
-            typeArguments,
-            _abstractValueDomain.dynamicType);
-        add(typeInfo);
-        constructorArguments.add(typeInfo);
       }
-
       newObject = new HCreate(cls, constructorArguments,
           _abstractValueDomain.createNonNullExact(cls), sourceInformation,
           instantiatedTypes: instantiatedTypes,
@@ -3048,6 +3054,12 @@
     if (!_rtiNeed.classNeedsTypeArguments(type.element) || type.treatAsRaw) {
       return object;
     }
+    if (options.experimentNewRti) {
+      // TODO(sra): For new-rti, construct Rti object. [type] could be `List<T>`
+      // or `JSArray<T>`.
+
+      // For now, fall through.
+    }
     List<HInstruction> arguments = <HInstruction>[];
     for (DartType argument in type.typeArguments) {
       arguments.add(_typeBuilder.analyzeTypeArgument(argument, sourceElement));
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 6e81b1c..7273a7d 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -3386,6 +3386,22 @@
   }
 
   @override
+  visitInstanceEnvironment(HInstanceEnvironment node) {
+    use(node.inputs.single);
+    js.Expression receiver = pop();
+    // TODO(sra): Optimize to direct field access where possible.
+    //
+    //    push(js.js(r'#.#', [receiver, _namer.rtiFieldJsName]));
+
+    FunctionEntity helperElement = _commonElements.instanceType;
+    _registry.registerStaticUse(
+        new StaticUse.staticInvoke(helperElement, CallStructure.ONE_ARG));
+    js.Expression helper = _emitter.staticFunctionAccess(helperElement);
+    push(js.js(r'#(#)', [helper, receiver]).withSourceInformation(
+        node.sourceInformation));
+  }
+
+  @override
   visitTypeEval(HTypeEval node) {
     // Call `env._eval("recipe")`.
     use(node.inputs[0]);
@@ -3415,7 +3431,7 @@
     use(node.inputs[1]);
     js.Expression extensions = pop();
 
-    MemberEntity method = _commonElements.rtiEvalMethod;
+    MemberEntity method = _commonElements.rtiBindMethod;
     Selector selector = Selector.fromElement(method);
     js.Name methodLiteral = _namer.invocationName(selector);
     push(js.js('#.#(#)', [
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index be6a444..08213ad 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -110,6 +110,7 @@
   R visitAsCheck(HAsCheck node);
   R visitSubtypeCheck(HSubtypeCheck node);
   R visitLoadType(HLoadType node);
+  R visitInstanceEnvironment(HInstanceEnvironment node);
   R visitTypeEval(HTypeEval node);
   R visitTypeBind(HTypeBind node);
 }
@@ -606,6 +607,8 @@
   @override
   visitLoadType(HLoadType node) => visitInstruction(node);
   @override
+  visitInstanceEnvironment(HInstanceEnvironment node) => visitInstruction(node);
+  @override
   visitTypeEval(HTypeEval node) => visitInstruction(node);
   @override
   visitTypeBind(HTypeBind node) => visitInstruction(node);
@@ -1091,8 +1094,9 @@
   static const int AS_CHECK_TYPECODE = 48;
   static const int SUBTYPE_CHECK_TYPECODE = 49;
   static const int LOAD_TYPE_TYPECODE = 50;
-  static const int TYPE_EVAL_TYPECODE = 51;
-  static const int TYPE_BIND_TYPECODE = 52;
+  static const int INSTANCE_ENVIRONMENT_TYPECODE = 51;
+  static const int TYPE_EVAL_TYPECODE = 52;
+  static const int TYPE_BIND_TYPECODE = 53;
 
   HInstruction(this.inputs, this.instructionType)
       : id = idCounter++,
@@ -4465,6 +4469,33 @@
   String toString() => 'HLoadType($typeExpression)';
 }
 
+/// The reified Rti environment stored on a class instance.
+///
+/// Classes with reified type arguments have the type environment stored on the
+/// instance. The reified environment is typically stored as the instance type,
+/// e.g. "UnmodifiableListView<int>".
+class HInstanceEnvironment extends HRtiInstruction {
+  HInstanceEnvironment(HInstruction instance, AbstractValue type)
+      : super([instance], type) {
+    setUseGvn();
+  }
+
+  @override
+  accept(HVisitor visitor) => visitor.visitInstanceEnvironment(this);
+
+  @override
+  int typeCode() => HInstruction.INSTANCE_ENVIRONMENT_TYPECODE;
+
+  @override
+  bool typeEquals(HInstruction other) => other is HInstanceEnvironment;
+
+  @override
+  bool dataEquals(HInstanceEnvironment other) => true;
+
+  @override
+  String toString() => 'HInstanceEnvironment()';
+}
+
 /// Evaluates an Rti type recipe in an Rti environment.
 class HTypeEval extends HRtiInstruction {
   TypeEnvironmentStructure envStructure;
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index e90128d..865c0ff 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -636,6 +636,8 @@
       return splitInstruction;
     }
 
+    // TODO(sra): Implement tagging with `JSArray<String>` for new rti.
+
     node.block.addBefore(node, splitInstruction);
 
     HInstruction stringTypeInfo = new HTypeInfoExpression(
diff --git a/pkg/compiler/lib/src/ssa/ssa_tracer.dart b/pkg/compiler/lib/src/ssa/ssa_tracer.dart
index 539261d..7ca5ee2 100644
--- a/pkg/compiler/lib/src/ssa/ssa_tracer.dart
+++ b/pkg/compiler/lib/src/ssa/ssa_tracer.dart
@@ -675,38 +675,44 @@
   }
 
   @override
-  visitIsTest(HIsTest node) {
+  String visitIsTest(HIsTest node) {
     var inputs = node.inputs.map(temporaryId).join(', ');
     return "IsTest: $inputs";
   }
 
   @override
-  visitAsCheck(HAsCheck node) {
+  String visitAsCheck(HAsCheck node) {
     var inputs = node.inputs.map(temporaryId).join(', ');
     String error = node.isTypeError ? 'TypeError' : 'CastError';
     return "AsCheck: $error $inputs";
   }
 
   @override
-  visitSubtypeCheck(HSubtypeCheck node) {
+  String visitSubtypeCheck(HSubtypeCheck node) {
     var inputs = node.inputs.map(temporaryId).join(', ');
     return "SubtypeCheck: $inputs";
   }
 
   @override
-  visitLoadType(HLoadType node) {
+  String visitLoadType(HLoadType node) {
     var inputs = node.inputs.map(temporaryId).join(', ');
     return "LoadType: ${node.typeExpression}  $inputs";
   }
 
   @override
-  visitTypeEval(HTypeEval node) {
+  String visitInstanceEnvironment(HInstanceEnvironment node) {
+    var inputs = node.inputs.map(temporaryId).join(', ');
+    return "InstanceEnvironment: $inputs";
+  }
+
+  @override
+  String visitTypeEval(HTypeEval node) {
     var inputs = node.inputs.map(temporaryId).join(', ');
     return "TypeEval: ${node.typeExpression}  $inputs";
   }
 
   @override
-  visitTypeBind(HTypeBind node) {
+  String visitTypeBind(HTypeBind node) {
     var inputs = node.inputs.map(temporaryId).join(', ');
     return "TypeBind: $inputs";
   }
diff --git a/pkg/compiler/lib/src/ssa/type_builder.dart b/pkg/compiler/lib/src/ssa/type_builder.dart
index 11c31ec..03ad305 100644
--- a/pkg/compiler/lib/src/ssa/type_builder.dart
+++ b/pkg/compiler/lib/src/ssa/type_builder.dart
@@ -247,6 +247,10 @@
   HInstruction analyzeTypeArgument(
       DartType argument, MemberEntity sourceElement,
       {SourceInformation sourceInformation}) {
+    if (builder.options.experimentNewRti) {
+      return analyzeTypeArgumentNewRti(argument, sourceElement,
+          sourceInformation: sourceInformation);
+    }
     argument = argument.unaliased;
     if (argument.treatAsDynamic) {
       // Represent [dynamic] as [null].
@@ -279,25 +283,136 @@
       {SourceInformation sourceInformation}) {
     argument = argument.unaliased;
 
-    if (argument.containsFreeTypeVariables) {
-      // TODO(sra): Locate type environment.
-      HInstruction environment =
-          builder.graph.addConstantString("env", _closedWorld);
-      // TODO(sra): Determine environment structure from context.
-      TypeEnvironmentStructure structure = null;
-      HInstruction rti = HTypeEval(environment, structure,
-          TypeExpressionRecipe(argument), _abstractValueDomain.dynamicType)
+    if (!argument.containsTypeVariables) {
+      HInstruction rti = HLoadType(argument, _abstractValueDomain.dynamicType)
         ..sourceInformation = sourceInformation;
       builder.add(rti);
       return rti;
     }
+    // TODO(sra): Locate type environment.
+    _EnvironmentExpressionAndStructure environmentAccess =
+        _buildEnvironmentForType(argument, sourceElement,
+            sourceInformation: sourceInformation);
 
-    HInstruction rti = HLoadType(argument, _abstractValueDomain.dynamicType)
+    HInstruction rti = HTypeEval(
+        environmentAccess.expression,
+        environmentAccess.structure,
+        TypeExpressionRecipe(argument),
+        _abstractValueDomain.dynamicType)
       ..sourceInformation = sourceInformation;
     builder.add(rti);
     return rti;
   }
 
+  _EnvironmentExpressionAndStructure _buildEnvironmentForType(
+      DartType type, MemberEntity member,
+      {SourceInformation sourceInformation}) {
+    assert(type.containsTypeVariables);
+    // Build the environment for each access, and hope GVN reduces the larger
+    // number of expressions. Another option is to precompute the environment at
+    // procedure entry and optimize early-exits by sinking the precomputed
+    // environment.
+
+    // Split the type variables into class-scope and function-scope(s).
+    bool usesInstanceParameters = false;
+    InterfaceType interfaceType;
+    Set<TypeVariableType> parameters = Set();
+
+    void processTypeVariable(TypeVariableType type) {
+      ClassTypeVariableAccess typeVariableAccess;
+      if (type.element.typeDeclaration is ClassEntity) {
+        typeVariableAccess = computeTypeVariableAccess(member);
+        interfaceType = _closedWorld.elementEnvironment
+            .getThisType(type.element.typeDeclaration);
+      } else {
+        typeVariableAccess = ClassTypeVariableAccess.parameter;
+      }
+      switch (typeVariableAccess) {
+        case ClassTypeVariableAccess.parameter:
+          parameters.add(type);
+          return;
+        case ClassTypeVariableAccess.instanceField:
+          if (member != builder.targetElement) {
+            // When [member] is a field, we can either be generating a checked
+            // setter or inlining its initializer in a constructor. An
+            // initializer is never built standalone, so in that case [target]
+            // is not the [member] itself.
+            parameters.add(type);
+            return;
+          }
+          usesInstanceParameters = true;
+          return;
+        case ClassTypeVariableAccess.property:
+          usesInstanceParameters = true;
+          return;
+        default:
+          builder.reporter.internalError(
+              type.element, 'Unexpected type variable in static context.');
+      }
+    }
+
+    type.forEachTypeVariable(processTypeVariable);
+
+    HInstruction environment;
+    TypeEnvironmentStructure structure;
+
+    if (usesInstanceParameters) {
+      HInstruction target =
+          builder.localsHandler.readThis(sourceInformation: sourceInformation);
+      // TODO(sra): HInstanceEnvironment should probably take an interceptor to
+      // allow the getInterceptor call to be reused.
+      environment =
+          HInstanceEnvironment(target, _abstractValueDomain.dynamicType)
+            ..sourceInformation = sourceInformation;
+      builder.add(environment);
+      structure = FullTypeEnvironmentStructure(classType: interfaceType);
+    }
+
+    // TODO(sra): Visit parameters in source-order.
+    for (TypeVariableType parameter in parameters) {
+      Local typeVariableLocal =
+          builder.localsHandler.getTypeVariableAsLocal(parameter);
+      HInstruction access = builder.localsHandler
+          .readLocal(typeVariableLocal, sourceInformation: sourceInformation);
+
+      if (environment == null) {
+        environment = access;
+        structure = SingletonTypeEnvironmentStructure(parameter);
+      } else if (structure is SingletonTypeEnvironmentStructure) {
+        SingletonTypeEnvironmentStructure singletonStructure = structure;
+        // Convert a singleton environment into a singleton tuple and extend it
+        // via 'bind'. i.e. generate `env1._eval("@<0>")._bind(env2)` TODO(sra):
+        // Have a bind1 instruction.
+        // TODO(sra): Add a 'Rti._bind1' method to shorten and accelerate this
+        // common case.
+        HInstruction singletonTuple = HTypeEval(
+            environment,
+            structure,
+            FullTypeEnvironmentRecipe(types: [singletonStructure.variable]),
+            _abstractValueDomain.dynamicType)
+          ..sourceInformation = sourceInformation;
+        builder.add(singletonTuple);
+        environment =
+            HTypeBind(singletonTuple, access, _abstractValueDomain.dynamicType);
+        builder.add(environment);
+        structure = FullTypeEnvironmentStructure(
+            bindings: [singletonStructure.variable, parameter]);
+      } else if (structure is FullTypeEnvironmentStructure) {
+        FullTypeEnvironmentStructure fullStructure = structure;
+        environment =
+            HTypeBind(environment, access, _abstractValueDomain.dynamicType);
+        builder.add(environment);
+        structure = FullTypeEnvironmentStructure(
+            classType: fullStructure.classType,
+            bindings: [...fullStructure.bindings, parameter]);
+      } else {
+        builder.reporter.internalError(parameter.element, 'Unexpected');
+      }
+    }
+
+    return _EnvironmentExpressionAndStructure(environment, structure);
+  }
+
   /// Build a [HTypeConversion] for converting [original] to type [type].
   ///
   /// Invariant: [type] must be valid in the context.
@@ -357,8 +472,9 @@
     if (type.isVoid) return original;
     if (type == _closedWorld.commonElements.objectType) return original;
 
-    HInstruction reifiedType =
-        analyzeTypeArgumentNewRti(type, builder.sourceElement);
+    HInstruction reifiedType = analyzeTypeArgumentNewRti(
+        type, builder.sourceElement,
+        sourceInformation: sourceInformation);
     if (type is InterfaceType) {
       // TODO(sra): Under NNDB opt-in, this will be NonNullable.
       AbstractValue subtype =
@@ -374,3 +490,9 @@
     }
   }
 }
+
+class _EnvironmentExpressionAndStructure {
+  final HInstruction expression;
+  final TypeEnvironmentStructure structure;
+  _EnvironmentExpressionAndStructure(this.expression, this.structure);
+}
diff --git a/sdk/lib/_internal/js_runtime/lib/rti.dart b/sdk/lib/_internal/js_runtime/lib/rti.dart
index 84574d2ff..437bace 100644
--- a/sdk/lib/_internal/js_runtime/lib/rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/rti.dart
@@ -6,11 +6,17 @@
 library rti;
 
 import 'dart:_foreign_helper'
-    show JS, JS_EMBEDDED_GLOBAL, JS_GET_NAME, RAW_DART_FUNCTION_REF;
+    show
+        getInterceptor,
+        JS,
+        JS_BUILTIN,
+        JS_EMBEDDED_GLOBAL,
+        JS_GET_NAME,
+        RAW_DART_FUNCTION_REF;
 import 'dart:_interceptors' show JSArray, JSUnmodifiableArray;
 
 import 'dart:_js_embedded_names'
-    show JsGetName, RtiUniverseFieldNames, RTI_UNIVERSE;
+    show JsBuiltin, JsGetName, RtiUniverseFieldNames, RTI_UNIVERSE;
 
 import 'dart:_recipe_syntax';
 
@@ -57,15 +63,15 @@
 
   /// Method called from generated code to evaluate a type environment recipe in
   /// `this` type environment.
-  Rti _eval(String recipe) {
+  Rti _eval(recipe) {
     // TODO(sra): Clone the fast-path of _Universe.evalInEnvironment to here.
-    return _rtiEval(this, recipe);
+    return _rtiEval(this, _Utils.asString(recipe));
   }
 
   /// Method called from generated code to extend `this` type environment (an
   /// interface or binding Rti) with function type arguments (a singleton
   /// argument or tuple of arguments).
-  Rti _bind(Rti typeOrTuple) => _rtiBind(this, typeOrTuple);
+  Rti _bind(typeOrTuple) => _rtiBind(this, _castToRti(typeOrTuple));
 
   /// Method called from generated code to extend `this` type (as a singleton
   /// type environment) with function type arguments (a singleton argument or
@@ -89,9 +95,9 @@
   ///
   /// The zero initializer ensures dart2js type analysis considers [_kind] is
   /// non-nullable.
-  int _kind = 0;
+  Object /*int*/ _kind = 0;
 
-  static int _getKind(Rti rti) => rti._kind;
+  static int _getKind(Rti rti) => _Utils.asInt(rti._kind);
   static void _setKind(Rti rti, int kind) {
     rti._kind = kind;
   }
@@ -144,7 +150,7 @@
     return _Utils.asString(_getPrimary(rti));
   }
 
-  static JSArray _getInterfaceTypeArguments(rti) {
+  static JSArray _getInterfaceTypeArguments(Rti rti) {
     // The array is a plain JavaScript Array, otherwise we would need the type
     // `JSArray<Rti>` to exist before we could create the type `JSArray<Rti>`.
     assert(_getKind(rti) == kindInterface);
@@ -156,7 +162,7 @@
     return _castToRti(_getPrimary(rti));
   }
 
-  static JSArray _getBindingArguments(rti) {
+  static JSArray _getBindingArguments(Rti rti) {
     assert(_getKind(rti) == kindBinding);
     return JS('JSUnmodifiableArray', '#', _getRest(rti));
   }
@@ -202,7 +208,7 @@
     return new Rti();
   }
 
-  String _canonicalRecipe;
+  Object _canonicalRecipe;
 
   static String _getCanonicalRecipe(Rti rti) {
     var s = rti._canonicalRecipe;
@@ -232,7 +238,45 @@
 /// Evaluate a ground-term type.
 /// Called from generated code.
 Rti findType(String recipe) {
-  _Universe.eval(_theUniverse(), recipe);
+  return _Universe.eval(_theUniverse(), recipe);
+}
+
+/// Returns the Rti type of [object].
+/// Called from generated code.
+Rti instanceType(object) {
+  // TODO(sra): Add specializations of this method. One possible way is to
+  // arrange that the interceptor has a _getType method that is injected into
+  // DartObject, Interceptor and JSArray. Then this method can be replaced-by
+  // `getInterceptor(o)._getType(o)`, allowing interceptor optimizations to
+  // select the specialization.
+
+  if (_Utils.instanceOf(
+      object,
+      JS_BUILTIN(
+          'depends:none;effects:none;', JsBuiltin.dartObjectConstructor))) {
+    var rti = JS('', r'#[#]', object, JS_GET_NAME(JsGetName.RTI_NAME));
+    if (rti != null) return _castToRti(rti);
+    return _instanceTypeFromConstructor(JS('', '#.constructor', object));
+  }
+
+  if (_Utils.isArray(object)) {
+    var rti = JS('', r'#[#]', object, JS_GET_NAME(JsGetName.RTI_NAME));
+    // TODO(sra): Do we need to protect against an Array passed between two Dart
+    // programs loaded into the same JavaScript isolate (e.g. via JS-interop).
+    // FWIW, the legacy rti has this problem too. Perhaps JSArrays should use a
+    // program-local `symbol` for the type field.
+    if (rti != null) return _castToRti(rti);
+    // TODO(sra): Use JS_GET_NAME to access the recipe.
+    throw UnimplementedError('return JSArray<any>');
+  }
+
+  var interceptor = getInterceptor(object);
+  return _instanceTypeFromConstructor(JS('', '#.constructor', interceptor));
+}
+
+Rti _instanceTypeFromConstructor(constructor) {
+  // TODO(sra): Cache Rti on constructor.
+  return findType(JS('String', '#.name', constructor));
 }
 
 Type getRuntimeType(object) {
@@ -576,7 +620,7 @@
 
   /// [arguments] becomes owned by the created Rti.
   static Rti _lookupBindingRti(Object universe, Rti base, Object arguments) {
-    var newBase = base;
+    Rti newBase = base;
     var newArguments = arguments;
     if (Rti._getKind(base) == Rti.kindBinding) {
       newBase = Rti._getBindingBase(base);
@@ -737,11 +781,11 @@
   }
 
   // Field accessors for the parser.
-  static Object universe(Object parser) => JS('String', '#.u', parser);
+  static Object universe(Object parser) => JS('', '#.u', parser);
   static Rti environment(Object parser) => JS('Rti', '#.e', parser);
   static String recipe(Object parser) => JS('String', '#.r', parser);
   static Object stack(Object parser) => JS('', '#.s', parser);
-  static Object position(Object parser) => JS('int', '#.p', parser);
+  static int position(Object parser) => JS('int', '#.p', parser);
   static void setPosition(Object parser, int p) {
     JS('', '#.p = #', parser, p);
   }
@@ -1092,8 +1136,13 @@
   static bool isString(Object o) => JS('bool', 'typeof # == "string"', o);
   static bool isNum(Object o) => JS('bool', 'typeof # == "number"', o);
 
+  static bool instanceOf(Object o, Object constructor) =>
+      JS('bool', '# instanceof #', o, constructor);
+
   static bool isIdentical(s, t) => JS('bool', '# === #', s, t);
 
+  static bool isArray(Object o) => JS('bool', 'Array.isArray(#)', o);
+
   static int arrayLength(Object array) => JS('int', '#.length', array);
 
   static Object arrayAt(Object array, int i) => JS('', '#[#]', array, i);
diff --git a/tests/compiler/dart2js_extra/rti/runtime_type_1_test.dart b/tests/compiler/dart2js_extra/rti/runtime_type_1_test.dart
new file mode 100644
index 0000000..1eb67ab
--- /dev/null
+++ b/tests/compiler/dart2js_extra/rti/runtime_type_1_test.dart
@@ -0,0 +1,28 @@
+// 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.
+//
+// dart2jsOptions=--experiment-new-rti --no-minify
+
+import 'dart:_rti' as rti;
+import "package:expect/expect.dart";
+
+class Thingy {}
+
+class GenericThingy<A, B> {
+  toString() => 'GenericThingy<$A, $B>';
+}
+
+main() {
+  var rti1 = rti.instanceType(1);
+  Expect.equals('JSInt', rti.testingRtiToString(rti1));
+
+  var rti2 = rti.instanceType('Hello');
+  Expect.equals('JSString', rti.testingRtiToString(rti2));
+
+  var rti3 = rti.instanceType(Thingy());
+  Expect.equals('Thingy', rti.testingRtiToString(rti3));
+
+  var rti4 = rti.instanceType(GenericThingy<String, int>());
+  Expect.equals('GenericThingy<String, int>', rti.testingRtiToString(rti4));
+}