blob: 86e50c6a7342677e45c188bb66bbeb8f073251fd [file] [log] [blame]
// Copyright (c) 2017, 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.
/// Handling of native code and entry points.
library vm.transformations.type_flow.native_code;
import 'dart:convert' show json;
import 'dart:core' hide Type;
import 'dart:io' show File;
import 'package:kernel/ast.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 'calls.dart';
import 'types.dart';
import 'utils.dart';
abstract class EntryPointsListener {
/// Add call by the given selector with arbitrary ('raw') arguments.
void addRawCall(Selector selector);
/// Sets the type of the given field.
void addDirectFieldAccess(Field field, Type value);
/// Add instantiation of the given class.
ConcreteType addAllocatedClass(Class c);
}
enum PragmaEntryPointType { Always, GetterOnly, SetterOnly }
abstract class EntryPointsAnnotationMatcher {
PragmaEntryPointType annotationsDefineRoot(List<Expression> annotations);
}
class ConstantEntryPointsAnnotationMatcher
implements EntryPointsAnnotationMatcher {
final CoreTypes coreTypes;
ConstantEntryPointsAnnotationMatcher(this.coreTypes);
PragmaEntryPointType definesRoot(InstanceConstant constant) {
if (constant.classReference.node != coreTypes.pragmaClass) return null;
Constant name = constant.fieldValues[coreTypes.pragmaName.reference];
assertx(name != null);
if (name is! StringConstant ||
(name as StringConstant).value != "vm.entry-point") {
return null;
}
Constant options = constant.fieldValues[coreTypes.pragmaOptions.reference];
assertx(options != null);
if (options is NullConstant) return PragmaEntryPointType.Always;
if (options is BoolConstant && options.value == true) {
return PragmaEntryPointType.Always;
}
if (options is StringConstant) {
if (options.value == "get") {
return PragmaEntryPointType.GetterOnly;
} else if (options.value == "set") {
return PragmaEntryPointType.SetterOnly;
} else {
throw "Error: string directive to @pragma('vm.entry-point', ...) must be either 'get' or 'set'.";
}
}
return null;
}
@override
PragmaEntryPointType annotationsDefineRoot(List<Expression> annotations) {
for (var annotation in annotations) {
if (annotation is ConstantExpression) {
Constant constant = annotation.constant;
if (constant is InstanceConstant) {
var type = definesRoot(constant);
if (type != null) return type;
}
} else {
throw "All annotations must be constants!";
}
}
return null;
}
}
class PragmaEntryPointsVisitor extends RecursiveVisitor {
final EntryPointsListener entryPoints;
final NativeCodeOracle nativeCodeOracle;
final EntryPointsAnnotationMatcher matcher;
Class currentClass = null;
PragmaEntryPointsVisitor(
this.entryPoints, this.nativeCodeOracle, this.matcher);
@override
visitClass(Class klass) {
var type = matcher.annotationsDefineRoot(klass.annotations);
if (type != null) {
if (type != PragmaEntryPointType.Always) {
throw "Error: pragma entry-point definition on a class must evaluate to null, true or false. See entry_points_pragma.md.";
}
entryPoints.addAllocatedClass(klass);
}
currentClass = klass;
klass.visitChildren(this);
}
@override
visitProcedure(Procedure proc) {
var type = matcher.annotationsDefineRoot(proc.annotations);
if (type != null) {
if (type != PragmaEntryPointType.Always) {
throw "Error: pragma entry-point definition on a procedure (including getters and setters) must evaluate to null, true or false. See entry_points_pragma.md.";
}
var callKind = proc.isGetter
? CallKind.PropertyGet
: (proc.isSetter ? CallKind.PropertySet : CallKind.Method);
entryPoints.addRawCall(proc.isInstanceMember
? new InterfaceSelector(proc, callKind: callKind)
: new DirectSelector(proc, callKind: callKind));
nativeCodeOracle.setMemberReferencedFromNativeCode(proc);
}
}
@override
visitConstructor(Constructor ctor) {
var type = matcher.annotationsDefineRoot(ctor.annotations);
if (type != null) {
if (type != PragmaEntryPointType.Always) {
throw "Error: pragma entry-point definition on a constructor must evaluate to null, true or false. See entry_points_pragma.md.";
}
entryPoints
.addRawCall(new DirectSelector(ctor, callKind: CallKind.Method));
entryPoints.addAllocatedClass(currentClass);
nativeCodeOracle.setMemberReferencedFromNativeCode(ctor);
}
}
@override
visitField(Field field) {
var type = matcher.annotationsDefineRoot(field.annotations);
if (type == null) return;
void addSelector(CallKind ck) {
entryPoints.addRawCall(field.isInstanceMember
? new InterfaceSelector(field, callKind: ck)
: new DirectSelector(field, callKind: ck));
}
switch (type) {
case PragmaEntryPointType.GetterOnly:
addSelector(CallKind.PropertyGet);
break;
case PragmaEntryPointType.SetterOnly:
addSelector(CallKind.PropertySet);
break;
case PragmaEntryPointType.Always:
addSelector(CallKind.PropertyGet);
addSelector(CallKind.PropertySet);
break;
}
nativeCodeOracle.setMemberReferencedFromNativeCode(field);
}
}
/// Provides insights into the behavior of native code.
class NativeCodeOracle {
final Map<String, List<Map<String, dynamic>>> _nativeMethods =
<String, List<Map<String, dynamic>>>{};
final LibraryIndex _libraryIndex;
final Set<Member> _membersReferencedFromNativeCode = new Set<Member>();
NativeCodeOracle(this._libraryIndex);
void setMemberReferencedFromNativeCode(Member member) {
_membersReferencedFromNativeCode.add(member);
}
bool isMemberReferencedFromNativeCode(Member member) =>
_membersReferencedFromNativeCode.contains(member);
/// Simulate the execution of a native method by adding its entry points
/// using [entryPointsListener]. Returns result type of the native method.
Type handleNativeProcedure(
Member member, EntryPointsListener entryPointsListener) {
final String nativeName = getExternalName(member);
Type returnType = null;
final nativeActions = _nativeMethods[nativeName];
if (nativeActions != null) {
for (var action in nativeActions) {
if (action['action'] == 'return') {
final c = _libraryIndex.getClass(action['library'], action['class']);
final concreteClass = entryPointsListener.addAllocatedClass(c);
final nullable = action['nullable'];
if (nullable == false) {
returnType = concreteClass;
} else if ((nullable == true) || (nullable == null)) {
returnType = new Type.nullable(concreteClass);
} else {
throw 'Bad entry point: unexpected nullable: "$nullable" in $action';
}
} else {
_addRoot(action, entryPointsListener);
}
}
}
if (returnType != null) {
return returnType;
} else {
return new Type.fromStatic(member.function.returnType);
}
}
void _addRoot(
Map<String, String> rootDesc, EntryPointsListener entryPointsListener) {
final String library = rootDesc['library'];
final String class_ = rootDesc['class'];
final String name = rootDesc['name'];
final String action = rootDesc['action'];
final libraryIndex = _libraryIndex;
if ((action == 'create-instance') || ((action == null) && (name == null))) {
if (name != null) {
throw 'Bad entry point: unexpected "name" element in $rootDesc';
}
final Class cls = libraryIndex.getClass(library, class_);
if (cls.isAbstract) {
throw 'Bad entry point: abstract class listed in $rootDesc';
}
entryPointsListener.addAllocatedClass(cls);
} else if ((action == 'call') ||
(action == 'get') ||
(action == 'set') ||
((action == null) && (name != null))) {
if (name == null) {
throw 'Bad entry point: expected "name" element in $rootDesc';
}
final String prefix = {
'get': LibraryIndex.getterPrefix,
'set': LibraryIndex.setterPrefix
}[action] ??
'';
Member member;
if (class_ != null) {
final classDotPrefix = class_ + '.';
if ((name == class_) || name.startsWith(classDotPrefix)) {
// constructor
if (action != 'call' && action != null) {
throw 'Bad entry point: action "$action" is not applicable to'
' constructor in $rootDesc';
}
final constructorName =
(name == class_) ? '' : name.substring(classDotPrefix.length);
member = libraryIndex.getMember(library, class_, constructorName);
} else {
member = libraryIndex.tryGetMember(library, class_, prefix + name);
if (member == null) {
member = libraryIndex.getMember(library, class_, name);
}
}
} else {
member = libraryIndex.tryGetTopLevelMember(
library, /* unused */ null, prefix + name);
if (member == null) {
member = libraryIndex.getTopLevelMember(library, name);
}
}
assertx(member != null);
CallKind callKind;
if (action == null) {
if ((member is Field) || ((member is Procedure) && member.isGetter)) {
callKind = CallKind.PropertyGet;
} else if ((member is Procedure) && member.isSetter) {
callKind = CallKind.PropertySet;
} else {
callKind = CallKind.Method;
}
} else {
callKind = const {
'get': CallKind.PropertyGet,
'set': CallKind.PropertySet,
'call': CallKind.Method
}[action];
}
assertx(callKind != null);
final Selector selector = member.isInstanceMember
? new InterfaceSelector(member, callKind: callKind)
: new DirectSelector(member, callKind: callKind);
entryPointsListener.addRawCall(selector);
if ((action == null) && (member is Field) && !member.isFinal) {
Selector selector = member.isInstanceMember
? new InterfaceSelector(member, callKind: CallKind.PropertySet)
: new DirectSelector(member, callKind: CallKind.PropertySet);
entryPointsListener.addRawCall(selector);
}
_membersReferencedFromNativeCode.add(member);
} else {
throw 'Bad entry point: unrecognized action "$action" in $rootDesc';
}
}
/// Reads JSON describing entry points and native methods from [jsonString].
/// Adds all global entry points using [entryPointsListener].
///
/// The format of the JSON descriptor is described in
/// 'runtime/vm/compiler/aot/entry_points_json.md'.
void processEntryPointsJSON(
String jsonString, EntryPointsListener entryPointsListener) {
final jsonObject = json.decode(jsonString);
final roots = jsonObject['roots'];
if (roots != null) {
for (var root in roots) {
_addRoot(new Map<String, String>.from(root), entryPointsListener);
}
}
final nativeMethods = jsonObject['native-methods'];
if (nativeMethods != null) {
nativeMethods.forEach((name, actions) {
_nativeMethods[name] = new List<Map<String, dynamic>>.from(
actions.map((action) => new Map<String, dynamic>.from(action)));
});
}
}
/// Reads JSON files [jsonFiles] describing entry points and native methods.
/// Adds all global entry points using [entryPointsListener].
///
/// The format of the JSON descriptor is described in
/// 'runtime/vm/compiler/aot/entry_points_json.md'.
void processEntryPointsJSONFiles(
List<String> jsonFiles, EntryPointsListener entryPointsListener) {
for (var file in jsonFiles) {
processEntryPointsJSON(
new File(file).readAsStringSync(), entryPointsListener);
}
}
}