[vm/kernel/bytecode] Generate bytecode for native methods

Change-Id: If47ef9ef4ff5ac3cb3f4f6737590370b647fc9ff
Reviewed-on: https://dart-review.googlesource.com/59180
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: RĂ©gis Crelier <regis@google.com>
diff --git a/pkg/kernel/lib/external_name.dart b/pkg/kernel/lib/external_name.dart
new file mode 100644
index 0000000..4da52a8
--- /dev/null
+++ b/pkg/kernel/lib/external_name.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2018, 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.
+
+library kernel.external_name;
+
+import 'ast.dart';
+
+/// Returns external (native) name of given [Member].
+String getExternalName(Member procedure) {
+  // Native procedures are marked as external and have an annotation,
+  // which looks like this:
+  //
+  //    import 'dart:_internal' as internal;
+  //
+  //    @internal.ExternalName("<name-of-native>")
+  //    external Object foo(arg0, ...);
+  //
+  if (!procedure.isExternal) {
+    return null;
+  }
+  for (final Expression annotation in procedure.annotations) {
+    if (annotation is ConstructorInvocation) {
+      if (_isExternalName(annotation.target.enclosingClass)) {
+        return (annotation.arguments.positional.single as StringLiteral).value;
+      }
+    } else if (annotation is ConstantExpression) {
+      final constant = annotation.constant;
+      if (constant is InstanceConstant) {
+        if (_isExternalName(constant.klass)) {
+          return (constant.fieldValues.values.single as StringConstant).value;
+        }
+      }
+    }
+  }
+  return null;
+}
+
+bool _isExternalName(Class klass) =>
+    klass.name == 'ExternalName' &&
+    klass.enclosingLibrary.importUri.toString() == 'dart:_internal';
diff --git a/pkg/kernel/lib/transformations/constants.dart b/pkg/kernel/lib/transformations/constants.dart
index de851c6..b8a3aa8 100644
--- a/pkg/kernel/lib/transformations/constants.dart
+++ b/pkg/kernel/lib/transformations/constants.dart
@@ -20,13 +20,13 @@
 
 import 'dart:io' as io;
 
-import '../kernel.dart';
 import '../ast.dart';
+import '../class_hierarchy.dart';
 import '../core_types.dart';
+import '../external_name.dart' show getExternalName;
+import '../kernel.dart';
 import '../type_algebra.dart';
 import '../type_environment.dart';
-import '../class_hierarchy.dart';
-import 'treeshaker.dart' show findNativeName;
 
 Component transformComponent(Component component, ConstantsBackend backend,
     {bool keepFields: false,
@@ -940,7 +940,7 @@
   visitStaticInvocation(StaticInvocation node) {
     final Procedure target = node.target;
     if (target.kind == ProcedureKind.Factory) {
-      final String nativeName = findNativeName(target);
+      final String nativeName = getExternalName(target);
       if (nativeName != null) {
         final Constant constant = backend.buildConstantForNative(
             nativeName,
diff --git a/pkg/kernel/lib/transformations/treeshaker.dart b/pkg/kernel/lib/transformations/treeshaker.dart
index f708bd9..2d50eda 100644
--- a/pkg/kernel/lib/transformations/treeshaker.dart
+++ b/pkg/kernel/lib/transformations/treeshaker.dart
@@ -1246,37 +1246,3 @@
 /// Exception that is thrown to stop the tree shaking analysis when a use
 /// of `dart:mirrors` is found.
 class _UsingMirrorsException {}
-
-String findNativeName(Member procedure) {
-  // Native procedures are marked as external and have an annotation,
-  // which looks like this:
-  //
-  //    import 'dart:_internal' as internal;
-  //
-  //    @internal.ExternalName("<name-of-native>")
-  //    external Object foo(arg0, ...);
-  //
-  if (procedure.isExternal) {
-    for (final Expression annotation in procedure.annotations) {
-      if (annotation is ConstructorInvocation) {
-        final Class klass = annotation.target.enclosingClass;
-        if (klass.name == 'ExternalName' &&
-            klass.enclosingLibrary.importUri.toString() == 'dart:_internal') {
-          assert(annotation.arguments.positional.length == 1);
-          return (annotation.arguments.positional[0] as StringLiteral).value;
-        }
-      } else if (annotation is ConstantExpression) {
-        final constant = annotation.constant;
-        if (constant is InstanceConstant) {
-          final Class klass = constant.klass;
-          if (klass.name == 'ExternalName' &&
-              klass.enclosingLibrary.importUri.toString() == 'dart:_internal') {
-            assert(constant.fieldValues.length == 1);
-            return (constant.fieldValues.values.single as StringConstant).value;
-          }
-        }
-      }
-    }
-  }
-  return null;
-}
diff --git a/pkg/vm/lib/bytecode/assembler.dart b/pkg/vm/lib/bytecode/assembler.dart
index fdf239e..d5fa43b 100644
--- a/pkg/vm/lib/bytecode/assembler.dart
+++ b/pkg/vm/lib/bytecode/assembler.dart
@@ -246,8 +246,8 @@
     emitWord(_encodeAD(Opcode.kPushPolymorphicInstanceCallByRange, ra, rd));
   }
 
-  void emitNativeCall(int ra, int rb, int rc) {
-    emitWord(_encodeABC(Opcode.kNativeCall, ra, rb, rc));
+  void emitNativeCall(int rd) {
+    emitWord(_encodeD(Opcode.kNativeCall, rd));
   }
 
   void emitOneByteStringFromCharCode(int ra, int rx) {
diff --git a/pkg/vm/lib/bytecode/constant_pool.dart b/pkg/vm/lib/bytecode/constant_pool.dart
index 6de7663..b6b0ad1 100644
--- a/pkg/vm/lib/bytecode/constant_pool.dart
+++ b/pkg/vm/lib/bytecode/constant_pool.dart
@@ -153,6 +153,11 @@
   Byte tag = 22;
 }
 
+type ConstantNativeEntry extends ConstantPoolEntry {
+  Byte tag = 23;
+  StringReference nativeName;
+}
+
 */
 
 enum ConstantTag {
@@ -179,6 +184,7 @@
   kContextOffset,
   kClosureFunction,
   kEndClosureFunctionScope,
+  kNativeEntry,
 }
 
 abstract class ConstantPoolEntry {
@@ -243,6 +249,8 @@
         return new ConstantClosureFunction.readFromBinary(source);
       case ConstantTag.kEndClosureFunctionScope:
         return new ConstantEndClosureFunctionScope.readFromBinary(source);
+      case ConstantTag.kNativeEntry:
+        return new ConstantNativeEntry.readFromBinary(source);
     }
     throw 'Unexpected constant tag $tag';
   }
@@ -1006,6 +1014,33 @@
   // [hashCode] and [operator ==].
 }
 
+class ConstantNativeEntry extends ConstantPoolEntry {
+  final String nativeName;
+
+  ConstantNativeEntry(this.nativeName);
+
+  @override
+  ConstantTag get tag => ConstantTag.kNativeEntry;
+
+  @override
+  void writeValueToBinary(BinarySink sink) {
+    sink.writeStringReference(nativeName);
+  }
+
+  ConstantNativeEntry.readFromBinary(BinarySource source)
+      : nativeName = source.readStringReference();
+
+  @override
+  String toString() => 'NativeEntry $nativeName';
+
+  @override
+  int get hashCode => nativeName.hashCode;
+
+  @override
+  bool operator ==(other) =>
+      other is ConstantNativeEntry && this.nativeName == other.nativeName;
+}
+
 class ConstantPool {
   final List<ConstantPoolEntry> entries = <ConstantPoolEntry>[];
   final Map<ConstantPoolEntry, int> _canonicalizationCache =
diff --git a/pkg/vm/lib/bytecode/dbc.dart b/pkg/vm/lib/bytecode/dbc.dart
index 53a6975..c2e6216 100644
--- a/pkg/vm/lib/bytecode/dbc.dart
+++ b/pkg/vm/lib/bytecode/dbc.dart
@@ -15,6 +15,9 @@
 //    parameters. This DBC instruction was removed at
 //    https://github.com/dart-lang/sdk/commit/cf1de7d46cd88e204380e8f96a993439be56b24c
 //
+// 3. NativeCall instruction is modified to have 'D' format and take 1 argument:
+//    D = index of NativeEntry constant pool entry
+//
 
 enum Opcode {
   kTrap,
@@ -302,7 +305,7 @@
   Opcode.kPushPolymorphicInstanceCallByRange: const Format(
       Encoding.kAD, const [Operand.imm, Operand.imm, Operand.none]),
   Opcode.kNativeCall: const Format(
-      Encoding.kABC, const [Operand.imm, Operand.imm, Operand.imm]),
+      Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
   Opcode.kOneByteStringFromCharCode: const Format(
       Encoding.kAX, const [Operand.reg, Operand.xeg, Operand.none]),
   Opcode.kStringToCharCode: const Format(
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index d2d12b3..797bc67 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -8,6 +8,7 @@
 import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
 import 'package:kernel/clone.dart';
 import 'package:kernel/core_types.dart' show CoreTypes;
+import 'package:kernel/external_name.dart' show getExternalName;
 import 'package:kernel/library_index.dart' show LibraryIndex;
 import 'package:kernel/transformations/constants.dart'
     show ConstantEvaluator, ConstantsBackend, EvaluationEnvironment;
@@ -103,7 +104,7 @@
 
   @override
   defaultMember(Member node) {
-    if (node.isAbstract || node.isExternal) {
+    if (node.isAbstract) {
       return;
     }
     try {
@@ -124,9 +125,17 @@
         if (node is Constructor) {
           _genConstructorInitializers(node);
         }
-        node.function?.body?.accept(this);
-        // TODO(alexmarkov): figure out when 'return null' should be generated.
-        _genPushNull();
+        if (node.isExternal) {
+          final String nativeName = getExternalName(node);
+          if (nativeName == null) {
+            return;
+          }
+          _genNativeCall(nativeName);
+        } else {
+          node.function?.body?.accept(this);
+          // TODO(alexmarkov): figure out when 'return null' should be generated.
+          _genPushNull();
+        }
         _genReturnTOS();
         end(node);
       }
@@ -137,6 +146,27 @@
     }
   }
 
+  void _genNativeCall(String nativeName) {
+    final function = enclosingMember.function;
+    assert(function != null);
+
+    if (locals.hasTypeArgsVar) {
+      asm.emitPush(locals.typeArgsVarIndexInFrame);
+    }
+    if (locals.hasReceiver) {
+      asm.emitPush(locals.getVarIndexInFrame(locals.receiverVar));
+    }
+    for (var param in function.positionalParameters) {
+      asm.emitPush(locals.getVarIndexInFrame(param));
+    }
+    for (var param in function.namedParameters) {
+      asm.emitPush(locals.getVarIndexInFrame(param));
+    }
+
+    final nativeEntryCpIndex = cp.add(new ConstantNativeEntry(nativeName));
+    asm.emitNativeCall(nativeEntryCpIndex);
+  }
+
   LibraryIndex _libraryIndex;
   LibraryIndex get libraryIndex =>
       _libraryIndex ??= new LibraryIndex.coreLibraries(component);
diff --git a/pkg/vm/lib/transformations/type_flow/native_code.dart b/pkg/vm/lib/transformations/type_flow/native_code.dart
index 8e4a171..cc078c6 100644
--- a/pkg/vm/lib/transformations/type_flow/native_code.dart
+++ b/pkg/vm/lib/transformations/type_flow/native_code.dart
@@ -10,12 +10,9 @@
 import 'dart:io' show File;
 
 import 'package:kernel/ast.dart';
-import 'package:kernel/library_index.dart' show LibraryIndex;
 import 'package:kernel/core_types.dart' show CoreTypes;
-
-// TODO(alexmarkov): Move findNativeName out of treeshaker and avoid dependency
-// on unrelated transformation.
-import 'package:kernel/transformations/treeshaker.dart' show findNativeName;
+import 'package:kernel/external_name.dart' show getExternalName;
+import 'package:kernel/library_index.dart' show LibraryIndex;
 
 import 'calls.dart';
 import 'types.dart';
@@ -108,7 +105,7 @@
   /// using [entryPointsListener]. Returns result type of the native method.
   Type handleNativeProcedure(
       Member member, EntryPointsListener entryPointsListener) {
-    final String nativeName = findNativeName(member);
+    final String nativeName = getExternalName(member);
     Type returnType = null;
 
     final nativeActions = _nativeMethods[nativeName];
diff --git a/pkg/vm/testcases/bytecode/bootstrapping.dart.expect b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
index c6c2c4e..8df59b8 100644
--- a/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
+++ b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
@@ -67,11 +67,44 @@
 ]  constructor _() → void
     : super core::Object::•()
     ;
-  @_in::ExternalName::•("Namespace_Create")
+[@vm.bytecode=
+Bytecode {
+  Entry                0
+  CheckStack
+  Push                 FP[-6]
+  Push                 FP[-5]
+  NativeCall           CP#0
+  ReturnTOS
+}
+ConstantPool {
+  [0] = NativeEntry Namespace_Create
+}
+]  @_in::ExternalName::•("Namespace_Create")
   external static method _create(self::_NamespaceImpl namespace, dynamic n) → self::_NamespaceImpl;
-  @_in::ExternalName::•("Namespace_GetPointer")
+[@vm.bytecode=
+Bytecode {
+  Entry                0
+  CheckStack
+  Push                 FP[-5]
+  NativeCall           CP#0
+  ReturnTOS
+}
+ConstantPool {
+  [0] = NativeEntry Namespace_GetPointer
+}
+]  @_in::ExternalName::•("Namespace_GetPointer")
   external static method _getPointer(self::_NamespaceImpl namespace) → core::int;
-  @_in::ExternalName::•("Namespace_GetDefault")
+[@vm.bytecode=
+Bytecode {
+  Entry                0
+  CheckStack
+  NativeCall           CP#0
+  ReturnTOS
+}
+ConstantPool {
+  [0] = NativeEntry Namespace_GetDefault
+}
+]  @_in::ExternalName::•("Namespace_GetDefault")
   external static method _getDefault() → core::int;
 [@vm.bytecode=
 Bytecode {
@@ -512,7 +545,18 @@
 }
 ]static field core::int _stderrFD = 2;
 static field core::String _rawScript;
-@_in::ExternalName::•("Builtin_PrintString")
+[@vm.bytecode=
+Bytecode {
+  Entry                0
+  CheckStack
+  Push                 FP[-5]
+  NativeCall           CP#0
+  ReturnTOS
+}
+ConstantPool {
+  [0] = NativeEntry Builtin_PrintString
+}
+]@_in::ExternalName::•("Builtin_PrintString")
 external static method _printString(core::String s) → void;
 [@vm.bytecode=
 Bytecode {