[vm] Support closurization of methods through @pragma entry-points.

Fixes dartbug.com/35720.

Also enable native extensions in AOT.
Increases Flutter Gallery snapshot size by 0.19%.

Change-Id: I1b24c3b9a49a13fd48452e9a89595516a7f01780
Cq-Include-Trybots: luci.dart.try:vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-precomp-mac-release-simarm64-try, vm-kernel-precomp-win-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/92283
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Samir Jindel 2019-02-14 17:47:28 +00:00 committed by commit-bot@chromium.org
parent a5d396cd25
commit ae7bf9e999
15 changed files with 301 additions and 114 deletions

View file

@ -13,7 +13,7 @@ const kNonNullableResultType = "vm:non-nullable-result-type";
abstract class ParsedPragma {}
enum PragmaEntryPointType { Always, GetterOnly, SetterOnly }
enum PragmaEntryPointType { Default, GetterOnly, SetterOnly, CallOnly }
class ParsedEntryPointPragma extends ParsedPragma {
final PragmaEntryPointType type;
@ -73,17 +73,20 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser {
case kEntryPointPragmaName:
PragmaEntryPointType type;
if (options is NullConstant) {
type = PragmaEntryPointType.Always;
type = PragmaEntryPointType.Default;
} else if (options is BoolConstant && options.value == true) {
type = PragmaEntryPointType.Always;
type = PragmaEntryPointType.Default;
} else if (options is StringConstant) {
if (options.value == "get") {
type = PragmaEntryPointType.GetterOnly;
} else if (options.value == "set") {
type = PragmaEntryPointType.SetterOnly;
} else if (options.value == "call") {
type = PragmaEntryPointType.CallOnly;
} else {
throw "Error: string directive to @pragma('$kEntryPointPragmaName', ...) "
"must be either 'get' or 'set'.";
"must be either 'get' or 'set' for fields "
"or 'get' or 'call' for procedures.";
}
}
return type != null ? new ParsedEntryPointPragma(type) : null;

View file

@ -58,7 +58,7 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
if (!klass.isAbstract) {
var type = _annotationsDefineRoot(klass.annotations);
if (type != null) {
if (type != PragmaEntryPointType.Always) {
if (type != PragmaEntryPointType.Default) {
throw "Error: pragma entry-point definition on a class must evaluate "
"to null, true or false. See entry_points_pragma.md.";
}
@ -73,17 +73,39 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
visitProcedure(Procedure proc) {
var type = _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.";
void addSelector(CallKind ck) {
entryPoints.addRawCall(proc.isInstanceMember
? new InterfaceSelector(proc, callKind: ck)
: new DirectSelector(proc, callKind: ck));
}
var callKind = proc.isGetter
final defaultCallKind = proc.isGetter
? CallKind.PropertyGet
: (proc.isSetter ? CallKind.PropertySet : CallKind.Method);
entryPoints.addRawCall(proc.isInstanceMember
? new InterfaceSelector(proc, callKind: callKind)
: new DirectSelector(proc, callKind: callKind));
switch (type) {
case PragmaEntryPointType.CallOnly:
addSelector(defaultCallKind);
break;
case PragmaEntryPointType.SetterOnly:
if (!proc.isSetter) {
throw "Error: cannot generate a setter for a method or getter ($proc).";
}
addSelector(CallKind.PropertySet);
break;
case PragmaEntryPointType.GetterOnly:
if (proc.isSetter) {
throw "Error: cannot closurize a setter ($proc).";
}
addSelector(CallKind.PropertyGet);
break;
case PragmaEntryPointType.Default:
addSelector(defaultCallKind);
if (!proc.isSetter && !proc.isGetter) {
addSelector(CallKind.PropertyGet);
}
}
nativeCodeOracle.setMemberReferencedFromNativeCode(proc);
}
}
@ -92,9 +114,10 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
visitConstructor(Constructor ctor) {
var type = _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.";
if (type != PragmaEntryPointType.Default &&
type != PragmaEntryPointType.CallOnly) {
throw "Error: pragma entry-point definition on a constructor ($ctor) must"
"evaluate to null, true, false or 'call'. See entry_points_pragma.md.";
}
entryPoints
.addRawCall(new DirectSelector(ctor, callKind: CallKind.Method));
@ -125,12 +148,15 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor {
}
addSelector(CallKind.PropertySet);
break;
case PragmaEntryPointType.Always:
case PragmaEntryPointType.Default:
addSelector(CallKind.PropertyGet);
if (!field.isFinal) {
addSelector(CallKind.PropertySet);
}
break;
case PragmaEntryPointType.CallOnly:
throw "Error: can't generate invocation dispatcher for field $field"
"through @pragma('vm:entry-point')";
}
nativeCodeOracle.setMemberReferencedFromNativeCode(field);

View file

@ -49,7 +49,7 @@ class ExpressionPragmaAnnotationParser extends PragmaAnnotationParser {
case kEntryPointPragmaName:
// We ignore the option because we can't properly evaluate it, assume
// it's true.
return new ParsedEntryPointPragma(PragmaEntryPointType.Always);
return new ParsedEntryPointPragma(PragmaEntryPointType.Default);
case kExactResultTypePragmaName:
if (options is TypeLiteral) {
return new ParsedResultTypeByTypePragma(options.type);

View file

@ -45,7 +45,7 @@ class B extends self::A {
synthetic constructor •() → self::B
: super self::A::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasTearOffUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
[@vm.procedure-attributes.metadata=hasDynamicUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
return new self::T1::•();
}
no-such-method-forwarder get bar() → dynamic
@ -59,7 +59,7 @@ abstract class C extends core::Object {
synthetic constructor •() → self::C
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasTearOffUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
[@vm.procedure-attributes.metadata=hasDynamicUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
return new self::T2::•();
}
}
@ -78,7 +78,7 @@ class E extends core::Object implements self::A {
synthetic constructor •() → self::E
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasTearOffUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
[@vm.procedure-attributes.metadata=hasDynamicUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
return new self::T4::•();
}
no-such-method-forwarder get bar() → dynamic
@ -88,7 +88,7 @@ class F extends core::Object {
synthetic constructor •() → self::F
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
return new self::T2::•();
}
}
@ -96,7 +96,7 @@ class G extends core::Object {
synthetic constructor •() → self::G
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
[@vm.procedure-attributes.metadata=hasThisUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
return new self::T5::•();
}
}
@ -106,7 +106,7 @@ class H extends core::Object {
;
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method foo({[@vm.inferred-type.metadata=dart.core::_Smi] dynamic left = null, [@vm.inferred-type.metadata=dart.core::_Smi] dynamic right = null}) → dynamic
return new self::T6::•();
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false] method noSuchMethod(core::Invocation invocation) → dynamic {
return new self::T7::•();
}
}

View file

@ -16,6 +16,8 @@
abort(); \
}
bool isDartPrecompiledRuntime = true;
Dart_Handle GetCurrentLibrary() {
Dart_Handle libraries = Dart_GetLoadedLibraries();
CHECK(libraries);
@ -36,6 +38,13 @@ Dart_Handle GetCurrentLibrary() {
abort();
}
// Some invalid accesses are allowed in AOT since we don't retain @pragma
// annotations. Therefore we skip the negative tests in AOT.
#define FAIL(name, result) \
if (!isDartPrecompiledRuntime) { \
Fail(name, result); \
}
void Fail(const char* name, Dart_Handle result) {
ASSERT(Dart_IsApiError(result));
const char* error = Dart_GetError(result);
@ -43,37 +52,52 @@ void Fail(const char* name, Dart_Handle result) {
ASSERT(strstr(error, "It is illegal to access"));
}
void FailClosurize(const char* name, Dart_Handle result) {
#define FAIL_INVOKE_FIELD(name, result) \
if (!isDartPrecompiledRuntime) { \
FailInvokeField(name, result); \
}
void FailInvokeField(const char* name, Dart_Handle result) {
ASSERT(Dart_IsApiError(result));
const char* error = Dart_GetError(result);
ASSERT(strstr(error, name));
ASSERT(strstr(error, "Entry-points do not allow closurizing methods"));
ASSERT(strstr(error, "Entry-points do not allow invoking fields"));
}
void FailClosurizeConstructor(const char* name, Dart_Handle result) {
ASSERT(Dart_IsUnhandledExceptionError(result));
const char* error = Dart_GetError(result);
ASSERT(strstr(error, name));
ASSERT(strstr(error, "No static getter"));
}
void TestFields(Dart_Handle target) {
Fail("fld0", Dart_GetField(target, Dart_NewStringFromCString("fld0")));
Fail("fld0",
FAIL("fld0", Dart_GetField(target, Dart_NewStringFromCString("fld0")));
FAIL("fld0",
Dart_SetField(target, Dart_NewStringFromCString("fld0"), Dart_Null()));
Dart_Handle result =
Dart_Invoke(target, Dart_NewStringFromCString("fld0"), 0, nullptr);
FailClosurize("fld0", result);
FAIL_INVOKE_FIELD(
"fld0",
Dart_Invoke(target, Dart_NewStringFromCString("fld0"), 0, nullptr));
CHECK(Dart_GetField(target, Dart_NewStringFromCString("fld1")));
CHECK(Dart_SetField(target, Dart_NewStringFromCString("fld1"), Dart_Null()));
FailClosurize("fld1", Dart_Invoke(target, Dart_NewStringFromCString("fld1"),
0, nullptr));
FAIL_INVOKE_FIELD(
"fld1",
Dart_Invoke(target, Dart_NewStringFromCString("fld1"), 0, nullptr));
CHECK(Dart_GetField(target, Dart_NewStringFromCString("fld2")));
Fail("fld2",
FAIL("fld2",
Dart_SetField(target, Dart_NewStringFromCString("fld2"), Dart_Null()));
FailClosurize("fld2", Dart_Invoke(target, Dart_NewStringFromCString("fld2"),
0, nullptr));
FAIL_INVOKE_FIELD(
"fld2",
Dart_Invoke(target, Dart_NewStringFromCString("fld2"), 0, nullptr));
Fail("fld3", Dart_GetField(target, Dart_NewStringFromCString("fld3")));
FAIL("fld3", Dart_GetField(target, Dart_NewStringFromCString("fld3")));
CHECK(Dart_SetField(target, Dart_NewStringFromCString("fld3"), Dart_Null()));
FailClosurize("fld3", Dart_Invoke(target, Dart_NewStringFromCString("fld3"),
0, nullptr));
FAIL_INVOKE_FIELD(
"fld3",
Dart_Invoke(target, Dart_NewStringFromCString("fld3"), 0, nullptr));
}
void RunTests(Dart_NativeArguments arguments) {
@ -81,14 +105,14 @@ void RunTests(Dart_NativeArguments arguments) {
//////// Test allocation and constructor invocation.
Fail("C", Dart_GetClass(lib, Dart_NewStringFromCString("C")));
FAIL("C", Dart_GetClass(lib, Dart_NewStringFromCString("C")));
Dart_Handle D_class = Dart_GetClass(lib, Dart_NewStringFromCString("D"));
CHECK(D_class);
CHECK(Dart_Allocate(D_class));
Fail("D.", Dart_New(D_class, Dart_Null(), 0, nullptr));
FAIL("D.", Dart_New(D_class, Dart_Null(), 0, nullptr));
CHECK(Dart_New(D_class, Dart_NewStringFromCString("defined"), 0, nullptr));
Dart_Handle D =
@ -97,32 +121,51 @@ void RunTests(Dart_NativeArguments arguments) {
//////// Test actions against methods
Fail("fn0", Dart_Invoke(D, Dart_NewStringFromCString("fn0"), 0, nullptr));
FailClosurizeConstructor(
"defined", Dart_GetField(D_class, Dart_NewStringFromCString("defined")));
FailClosurizeConstructor(
"fact", Dart_GetField(D_class, Dart_NewStringFromCString("fact")));
FAIL("fn0", Dart_Invoke(D, Dart_NewStringFromCString("fn0"), 0, nullptr));
CHECK(Dart_Invoke(D, Dart_NewStringFromCString("fn1"), 0, nullptr));
FAIL("fn1", Dart_Invoke(D, Dart_NewStringFromCString("fn1_get"), 0, nullptr));
CHECK(Dart_Invoke(D, Dart_NewStringFromCString("fn1_call"), 0, nullptr));
Fail("get_fn0", Dart_GetField(D, Dart_NewStringFromCString("fn0")));
FAIL("fn0", Dart_GetField(D, Dart_NewStringFromCString("fn0")));
Fail("get_fn1", Dart_GetField(D, Dart_NewStringFromCString("fn1")));
CHECK(Dart_GetField(D, Dart_NewStringFromCString("fn1")));
CHECK(Dart_GetField(D, Dart_NewStringFromCString("fn1_get")));
FAIL("fn1", Dart_GetField(D, Dart_NewStringFromCString("fn1_call")));
Fail("fn2",
FAIL("fn2",
Dart_Invoke(D_class, Dart_NewStringFromCString("fn2"), 0, nullptr));
CHECK(Dart_Invoke(D_class, Dart_NewStringFromCString("fn3"), 0, nullptr));
CHECK(
Dart_Invoke(D_class, Dart_NewStringFromCString("fn3_call"), 0, nullptr));
FAIL("fn3",
Dart_Invoke(D_class, Dart_NewStringFromCString("fn3_get"), 0, nullptr));
FailClosurize("fn2",
Dart_GetField(D_class, Dart_NewStringFromCString("fn2")));
FAIL("fn2", Dart_GetField(D_class, Dart_NewStringFromCString("fn2")));
FailClosurize("fn3",
Dart_GetField(D_class, Dart_NewStringFromCString("fn3")));
CHECK(Dart_GetField(D_class, Dart_NewStringFromCString("fn3")));
FAIL("fn3_call",
Dart_GetField(D_class, Dart_NewStringFromCString("fn3_call")));
CHECK(Dart_GetField(D_class, Dart_NewStringFromCString("fn3_get")));
Fail("fn0", Dart_Invoke(lib, Dart_NewStringFromCString("fn0"), 0, nullptr));
FAIL("fn0", Dart_Invoke(lib, Dart_NewStringFromCString("fn0"), 0, nullptr));
CHECK(Dart_Invoke(lib, Dart_NewStringFromCString("fn1"), 0, nullptr));
FAIL("fn1",
Dart_Invoke(lib, Dart_NewStringFromCString("fn1_get"), 0, nullptr));
CHECK(Dart_Invoke(lib, Dart_NewStringFromCString("fn1_call"), 0, nullptr));
FailClosurize("fn0", Dart_GetField(lib, Dart_NewStringFromCString("fn0")));
FAIL("fn0", Dart_GetField(lib, Dart_NewStringFromCString("fn0")));
FailClosurize("fn1", Dart_GetField(lib, Dart_NewStringFromCString("fn1")));
CHECK(Dart_GetField(lib, Dart_NewStringFromCString("fn1")));
CHECK(Dart_GetField(lib, Dart_NewStringFromCString("fn1_get")));
FAIL("fn1", Dart_GetField(lib, Dart_NewStringFromCString("fn1_call")));
//////// Test actions against fields
@ -146,6 +189,8 @@ Dart_NativeFunction ResolveName(Dart_Handle name,
DART_EXPORT Dart_Handle
entrypoints_verification_test_extension_Init(Dart_Handle parent_library) {
isDartPrecompiledRuntime = Dart_IsPrecompiledRuntime();
if (Dart_IsError(parent_library)) {
return parent_library;
}

View file

@ -55,16 +55,24 @@ getters, setters and constructors):
@pragma("vm:entry-point")
@pragma("vm:entry-point", true/false)
@pragma("vm:entry-point", !const bool.formEnvironment("dart.vm.product"))
@pragma("vm:entry-point", "get")
@pragma("vm:entry-point", "call")
void foo() { ... }
```
If the second parameter is missing, `null` or `true`, the procedure will
available for lookup and invocation directly from native or VM code. If the
procedure is a *generative* constructor, the enclosing class must also be
If the second parameter is missing, `null` or `true`, the procedure (and its
closurized form, excluding constructors and setters) will available for lookup
and invocation directly from native or VM code.
If the procedure is a *generative* constructor, the enclosing class must also be
annotated for allocation from native or VM code.
Note that annotating a procedure does not allow closurizing it, e.g. access a
non-getter via `Dart_GetField`.
If the annotation is "get" or "call", the procedure will only be available for
closurization (access via `Dart_GetField`) or invocation (access via
`Dart_Invoke`).
"@pragma("vm:entry-point", "get") against constructors or setters is disallowed
since they cannot be closurized.
### Fields
@ -86,3 +94,5 @@ the interface of the enclosing class are marked for native invocation. If the
'get'/'set' parameter is used, only the getter/setter is marked. For static
fields, the implicit getter is always marked. The third form does not make sense
for static fields because they do not belong to an interface.
Note that no form of entry-point annotation allows invoking a field.

View file

@ -987,6 +987,7 @@ void Precompiler::AddAnnotatedRoots() {
auto& cls = Class::Handle(Z);
auto& members = Array::Handle(Z);
auto& function = Function::Handle(Z);
auto& function2 = Function::Handle(Z);
auto& field = Field::Handle(Z);
auto& metadata = Array::Handle(Z);
auto& reusable_object_handle = Object::Handle(Z);
@ -1051,13 +1052,23 @@ void Precompiler::AddAnnotatedRoots() {
if (function.has_pragma()) {
metadata ^= lib.GetMetadata(function);
if (metadata.IsNull()) continue;
if (FindEntryPointPragma(isolate(), metadata, &reusable_field_handle,
&reusable_object_handle) !=
EntryPointPragma::kAlways) {
continue;
auto type =
FindEntryPointPragma(isolate(), metadata, &reusable_field_handle,
&reusable_object_handle);
if (type == EntryPointPragma::kAlways ||
type == EntryPointPragma::kCallOnly) {
AddFunction(function);
}
if ((type == EntryPointPragma::kAlways ||
type == EntryPointPragma::kGetterOnly) &&
function.kind() != RawFunction::kConstructor &&
!function.IsSetterFunction()) {
function2 = function.ImplicitClosureFunction();
AddFunction(function2);
}
AddFunction(function);
if (function.IsGenerativeConstructor()) {
AddInstantiatedClass(cls);
}

View file

@ -3749,7 +3749,7 @@ static RawObject* ResolveConstructor(const char* current_func,
current_func, constr_name.ToCString(), error_message.ToCString()));
return ApiError::New(message);
}
RawError* error = constructor.VerifyEntryPoint();
RawError* error = constructor.VerifyCallEntryPoint();
if (error != Error::null()) return error;
return constructor.raw();
}
@ -4043,7 +4043,7 @@ DART_EXPORT Dart_Handle Dart_InvokeConstructor(Dart_Handle object,
if (!constructor.IsNull() && constructor.IsGenerativeConstructor() &&
constructor.AreValidArgumentCounts(
kTypeArgsLen, number_of_arguments + extra_args, 0, NULL)) {
CHECK_ERROR_HANDLE(constructor.VerifyEntryPoint());
CHECK_ERROR_HANDLE(constructor.VerifyCallEntryPoint());
// Create the argument list.
// Constructors get the uninitialized object.
if (!type_arguments.IsNull()) {

View file

@ -572,7 +572,9 @@ void KernelLoader::LoadNativeExtensionLibraries(
Instance& constant = Instance::Handle(Z);
String& uri_path = String::Handle(Z);
Library& library = Library::Handle(Z);
#if !defined(DART_PRECOMPILER)
Object& result = Object::Handle(Z);
#endif
for (intptr_t i = 0; i < length; ++i) {
library ^= potential_extension_libraries_.At(i);
@ -603,6 +605,7 @@ void KernelLoader::LoadNativeExtensionLibraries(
if (uri_path.IsNull()) continue;
#if !defined(DART_PRECOMPILER)
if (!I->HasTagHandler()) {
H.ReportError("no library handler registered.");
}
@ -614,6 +617,7 @@ void KernelLoader::LoadNativeExtensionLibraries(
if (result.IsError()) {
H.ReportError(Error::Cast(result), "library handler failed");
}
#endif
// Create a dummy library and add it as an import to the current library.
// This allows later to discover and reload this native extension, e.g.

View file

@ -3294,7 +3294,7 @@ RawObject* Class::InvokeGetter(const String& getter_name,
Function::Handle(zone, LookupStaticFunction(internal_getter_name));
if (field.IsNull() && !getter.IsNull() && check_is_entrypoint) {
CHECK_ERROR(getter.VerifyEntryPoint());
CHECK_ERROR(getter.VerifyCallEntryPoint());
}
if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) {
@ -3302,7 +3302,7 @@ RawObject* Class::InvokeGetter(const String& getter_name,
getter = LookupStaticFunction(getter_name);
if (!getter.IsNull()) {
if (check_is_entrypoint) {
CHECK_ERROR(EntryPointClosurizationError(getter_name));
CHECK_ERROR(getter.VerifyClosurizedEntryPoint());
}
if (getter.SafeToClosurize()) {
// Looking for a getter but found a regular method: closurize it.
@ -3360,7 +3360,7 @@ RawObject* Class::InvokeSetter(const String& setter_name,
const Function& setter =
Function::Handle(zone, LookupStaticFunction(internal_setter_name));
if (!setter.IsNull() && check_is_entrypoint) {
CHECK_ERROR(setter.VerifyEntryPoint());
CHECK_ERROR(setter.VerifyCallEntryPoint());
}
const int kNumArgs = 1;
const Array& args = Array::Handle(zone, Array::New(kNumArgs));
@ -3422,7 +3422,7 @@ RawObject* Class::Invoke(const String& function_name,
Function::Handle(zone, LookupStaticFunction(function_name));
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(function.VerifyEntryPoint());
CHECK_ERROR(function.VerifyCallEntryPoint());
}
if (function.IsNull()) {
@ -3432,7 +3432,7 @@ RawObject* Class::Invoke(const String& function_name,
check_is_entrypoint));
if (getter_result.raw() != Object::sentinel().raw()) {
if (check_is_entrypoint) {
CHECK_ERROR(EntryPointClosurizationError(function_name));
CHECK_ERROR(EntryPointFieldInvocationError(function_name));
}
// Make room for the closure (receiver) in the argument list.
const intptr_t num_args = args.Length();
@ -10612,7 +10612,35 @@ RawNamespace* Library::ImportAt(intptr_t index) const {
}
void Library::DropDependenciesAndCaches() const {
StorePointer(&raw_ptr()->imports_, Object::empty_array().raw());
// We need to preserve the "dart-ext:" imports because they are used by
// Loader::ReloadNativeExtensions().
intptr_t native_import_count = 0;
Array& imports = Array::Handle(raw_ptr()->imports_);
Namespace& ns = Namespace::Handle();
Library& lib = Library::Handle();
String& url = String::Handle();
for (int i = 0; i < imports.Length(); ++i) {
ns = Namespace::RawCast(imports.At(i));
if (ns.IsNull()) continue;
lib = ns.library();
url = lib.url();
if (url.StartsWith(Symbols::DartExtensionScheme())) {
native_import_count++;
}
}
Array& new_imports =
Array::Handle(Array::New(native_import_count, Heap::kOld));
for (int i = 0, j = 0; i < imports.Length(); ++i) {
ns = Namespace::RawCast(imports.At(i));
if (ns.IsNull()) continue;
lib = ns.library();
url = lib.url();
if (url.StartsWith(Symbols::DartExtensionScheme())) {
new_imports.SetAt(j++, ns);
}
}
StorePointer(&raw_ptr()->imports_, new_imports.raw());
StorePointer(&raw_ptr()->exports_, Object::empty_array().raw());
StoreNonPointer(&raw_ptr()->num_imports_, 0);
StorePointer(&raw_ptr()->resolved_names_, Array::null());
@ -10831,7 +10859,7 @@ RawObject* Library::InvokeGetter(const String& getter_name,
if (obj.IsFunction()) {
getter = Function::Cast(obj).raw();
if (check_is_entrypoint) {
CHECK_ERROR(getter.VerifyEntryPoint());
CHECK_ERROR(getter.VerifyCallEntryPoint());
}
} else {
obj = LookupLocalOrReExportObject(getter_name);
@ -10841,7 +10869,7 @@ RawObject* Library::InvokeGetter(const String& getter_name,
if (obj.IsFunction() && check_is_entrypoint) {
if (!getter_name.Equals(String::Handle(String::New("main"))) ||
raw() != Isolate::Current()->object_store()->root_library()) {
CHECK_ERROR(EntryPointClosurizationError(getter_name));
CHECK_ERROR(Function::Cast(obj).VerifyClosurizedEntryPoint());
}
}
if (obj.IsFunction() && Function::Cast(obj).SafeToClosurize()) {
@ -10912,7 +10940,7 @@ RawObject* Library::InvokeSetter(const String& setter_name,
}
if (!setter.IsNull() && check_is_entrypoint) {
CHECK_ERROR(setter.VerifyEntryPoint());
CHECK_ERROR(setter.VerifyCallEntryPoint());
}
const int kNumArgs = 1;
@ -10950,7 +10978,7 @@ RawObject* Library::Invoke(const String& function_name,
}
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(function.VerifyEntryPoint());
CHECK_ERROR(function.VerifyCallEntryPoint());
}
if (function.IsNull()) {
@ -10959,7 +10987,7 @@ RawObject* Library::Invoke(const String& function_name,
function_name, false, respect_reflectable, check_is_entrypoint));
if (getter_result.raw() != Object::sentinel().raw()) {
if (check_is_entrypoint) {
CHECK_ERROR(EntryPointClosurizationError(function_name));
CHECK_ERROR(EntryPointFieldInvocationError(function_name));
}
// Make room for the closure (receiver) in arguments.
intptr_t numArgs = args.Length();
@ -15682,7 +15710,7 @@ RawObject* Instance::InvokeGetter(const String& getter_name,
Function& function = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, internal_getter_name));
if (check_is_entrypoint) {
if (!function.IsNull() && check_is_entrypoint) {
// The getter must correspond to either an entry-point field or a getter
// method explicitly marked.
Field& field = Field::Handle(zone);
@ -15691,8 +15719,8 @@ RawObject* Instance::InvokeGetter(const String& getter_name,
}
if (!field.IsNull()) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
} else if (!function.IsNull()) {
CHECK_ERROR(function.VerifyEntryPoint());
} else {
CHECK_ERROR(function.VerifyCallEntryPoint());
}
}
@ -15701,7 +15729,7 @@ RawObject* Instance::InvokeGetter(const String& getter_name,
function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name);
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(EntryPointClosurizationError(getter_name));
CHECK_ERROR(function.VerifyClosurizedEntryPoint());
}
if (!function.IsNull() && function.SafeToClosurize()) {
@ -15750,7 +15778,7 @@ RawObject* Instance::InvokeSetter(const String& setter_name,
if (!field.IsNull()) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
} else if (!setter.IsNull()) {
CHECK_ERROR(setter.VerifyEntryPoint());
CHECK_ERROR(setter.VerifyCallEntryPoint());
}
}
@ -15778,7 +15806,7 @@ RawObject* Instance::Invoke(const String& function_name,
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, function_name));
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(function.VerifyEntryPoint());
CHECK_ERROR(function.VerifyCallEntryPoint());
}
// TODO(regis): Support invocation of generic functions with type arguments.
@ -15798,7 +15826,7 @@ RawObject* Instance::Invoke(const String& function_name,
function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name);
if (!function.IsNull()) {
if (check_is_entrypoint) {
CHECK_ERROR(EntryPointClosurizationError(function_name));
CHECK_ERROR(EntryPointFieldInvocationError(function_name));
}
ASSERT(function.kind() != RawFunction::kMethodExtractor);
// Invoke the getter.
@ -21681,15 +21709,19 @@ EntryPointPragma FindEntryPointPragma(Isolate* I,
if (pragma->raw() == Symbols::Set().raw()) {
return EntryPointPragma::kSetterOnly;
}
if (pragma->raw() == Symbols::Call().raw()) {
return EntryPointPragma::kCallOnly;
}
}
return EntryPointPragma::kNever;
}
DART_WARN_UNUSED_RESULT
RawError* VerifyEntryPoint(const Library& lib,
const Object& member,
const Object& annotated,
EntryPointPragma kind) {
RawError* VerifyEntryPoint(
const Library& lib,
const Object& member,
const Object& annotated,
std::initializer_list<EntryPointPragma> allowed_kinds) {
#if defined(DART_PRECOMPILED_RUNTIME)
// Annotations are discarded in the AOT snapshot, so we can't determine
// precisely if this member was marked as an entry-point. Instead, we use
@ -21713,13 +21745,23 @@ RawError* VerifyEntryPoint(const Library& lib,
EntryPointPragma pragma =
FindEntryPointPragma(Isolate::Current(), Array::Cast(metadata),
&Field::Handle(), &Object::Handle());
const bool is_marked_entrypoint =
pragma == kind || pragma == EntryPointPragma::kAlways;
bool is_marked_entrypoint = pragma == EntryPointPragma::kAlways;
if (!is_marked_entrypoint) {
for (const auto allowed_kind : allowed_kinds) {
if (pragma == allowed_kind) {
is_marked_entrypoint = true;
break;
}
}
}
#endif
if (!is_marked_entrypoint) {
const char* member_cstring =
member.IsFunction()
? Function::Cast(member).ToLibNamePrefixedQualifiedCString()
? OS::SCreate(
Thread::Current()->zone(), "%s (kind %s)",
Function::Cast(member).ToLibNamePrefixedQualifiedCString(),
Function::KindToCString(Function::Cast(member).kind()))
: member.ToCString();
char const* error = OS::SCreate(
Thread::Current()->zone(),
@ -21735,12 +21777,12 @@ RawError* VerifyEntryPoint(const Library& lib,
}
DART_WARN_UNUSED_RESULT
RawError* EntryPointClosurizationError(const String& getter_name) {
RawError* EntryPointFieldInvocationError(const String& getter_name) {
if (!FLAG_verify_entry_points) return Error::null();
char const* error = OS::SCreate(
Thread::Current()->zone(),
"ERROR: Entry-points do not allow closurizing methods "
"ERROR: Entry-points do not allow invoking fields "
"(failure to resolve '%s')\n"
"ERROR: See "
"https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
@ -21750,50 +21792,67 @@ RawError* EntryPointClosurizationError(const String& getter_name) {
return ApiError::New(String::Handle(String::New(error)));
}
RawError* Function::VerifyEntryPoint() const {
RawError* Function::VerifyCallEntryPoint() const {
if (!FLAG_verify_entry_points) return Error::null();
const Class& cls = Class::Handle(Owner());
const Library& lib = Library::Handle(cls.library());
switch (kind()) {
case RawFunction::kRegularFunction:
case RawFunction::kGetterFunction:
case RawFunction::kSetterFunction:
case RawFunction::kConstructor:
return dart::VerifyEntryPoint(lib, *this, *this,
EntryPointPragma::kAlways);
{EntryPointPragma::kCallOnly});
break;
case RawFunction::kImplicitGetter: {
const Field& accessed = Field::Handle(accessor_field());
return dart::VerifyEntryPoint(lib, *this, accessed,
EntryPointPragma::kGetterOnly);
case RawFunction::kGetterFunction:
return dart::VerifyEntryPoint(
lib, *this, *this,
{EntryPointPragma::kCallOnly, EntryPointPragma::kGetterOnly});
break;
}
case RawFunction::kImplicitSetter: {
const Field& accessed = Field::Handle(accessor_field());
return dart::VerifyEntryPoint(lib, *this, accessed,
EntryPointPragma::kSetterOnly);
case RawFunction::kImplicitGetter:
return dart::VerifyEntryPoint(lib, *this, Field::Handle(accessor_field()),
{EntryPointPragma::kGetterOnly});
break;
case RawFunction::kImplicitSetter:
return dart::VerifyEntryPoint(lib, *this, Field::Handle(accessor_field()),
{EntryPointPragma::kSetterOnly});
case RawFunction::kMethodExtractor:
return Function::Handle(extracted_method_closure())
.VerifyClosurizedEntryPoint();
break;
}
default:
return dart::VerifyEntryPoint(lib, *this, Object::Handle(),
EntryPointPragma::kAlways);
return dart::VerifyEntryPoint(lib, *this, Object::Handle(), {});
break;
}
}
RawError* Function::VerifyClosurizedEntryPoint() const {
if (!FLAG_verify_entry_points) return Error::null();
const Class& cls = Class::Handle(Owner());
const Library& lib = Library::Handle(cls.library());
switch (kind()) {
case RawFunction::kRegularFunction:
case RawFunction::kImplicitClosureFunction:
return dart::VerifyEntryPoint(lib, *this, *this,
{EntryPointPragma::kGetterOnly});
default:
UNREACHABLE();
}
}
RawError* Field::VerifyEntryPoint(EntryPointPragma pragma) const {
if (!FLAG_verify_entry_points) return Error::null();
const Class& cls = Class::Handle(Owner());
const Library& lib = Library::Handle(cls.library());
return dart::VerifyEntryPoint(lib, *this, *this, pragma);
return dart::VerifyEntryPoint(lib, *this, *this, {pragma});
}
RawError* Class::VerifyEntryPoint() const {
if (!FLAG_verify_entry_points) return Error::null();
const Library& lib = Library::Handle(library());
if (!lib.IsNull()) {
return dart::VerifyEntryPoint(lib, *this, *this, EntryPointPragma::kAlways);
return dart::VerifyEntryPoint(lib, *this, *this, {});
} else {
return Error::null();
}

View file

@ -2606,7 +2606,10 @@ class Function : public Object {
}
DART_WARN_UNUSED_RESULT
RawError* VerifyEntryPoint() const;
RawError* VerifyCallEntryPoint() const;
DART_WARN_UNUSED_RESULT
RawError* VerifyClosurizedEntryPoint() const;
static intptr_t InstanceSize() {
return RoundedAllocationSize(sizeof(RawFunction));
@ -2970,7 +2973,13 @@ class RedirectionData : public Object {
friend class HeapProfiler;
};
enum class EntryPointPragma { kAlways, kNever, kGetterOnly, kSetterOnly };
enum class EntryPointPragma {
kAlways,
kNever,
kGetterOnly,
kSetterOnly,
kCallOnly
};
class FfiTrampolineData : public Object {
public:
@ -9414,7 +9423,7 @@ EntryPointPragma FindEntryPointPragma(Isolate* I,
Object* reusable_object_handle);
DART_WARN_UNUSED_RESULT
RawError* EntryPointClosurizationError(const String& getter_name);
RawError* EntryPointFieldInvocationError(const String& getter_name);
} // namespace dart

View file

@ -907,7 +907,8 @@ class RawFunction : public RawObject {
RawArray* parameter_types_;
RawArray* parameter_names_;
RawTypeArguments* type_parameters_; // Array of TypeParameter.
RawObject* data_; // Additional data specific to the function kind.
RawObject* data_; // Additional data specific to the function kind. See
// Function::set_data() for details.
RawObject** to_snapshot(Snapshot::Kind kind) {
switch (kind) {
case Snapshot::kFullAOT:

View file

@ -368,6 +368,7 @@ class ObjectPointerVisitor;
V(Index, "index") \
V(DartScheme, "dart:") \
V(DartSchemePrivate, "dart:_") \
V(DartExtensionScheme, "dart-ext:") \
V(DartInternalPackage, "package:dart_internal/") \
V(DartNativeWrappers, "dart:nativewrappers") \
V(DartNativeWrappersLibName, "nativewrappers") \

View file

@ -37,11 +37,23 @@ class D {
@pragma("vm:entry-point")
void fn1() {}
@pragma("vm:entry-point", "get")
void fn1_get() {}
@pragma("vm:entry-point", "call")
void fn1_call() {}
static void fn2() {}
@pragma("vm:entry-point")
static void fn3() {}
@pragma("vm:entry-point", "call")
static void fn3_call() {}
@pragma("vm:entry-point", "get")
static void fn3_get() {}
void Function() fld0;
@pragma("vm:entry-point")
@ -59,6 +71,12 @@ void fn0() {}
@pragma("vm:entry-point")
void fn1() {}
@pragma("vm:entry-point", "get")
void fn1_get() {}
@pragma("vm:entry-point", "call")
void fn1_call() {}
class E extends D {
E.ctor();
}

View file

@ -225,5 +225,5 @@ io/socket_many_connections_test: Skip # Timeout
io/web_socket_compression_test: Skip # Timeout
io/web_socket_test: Skip # Timeout
[ $hot_reload || $hot_reload_rollback || $compiler != dartk && $compiler != dartkb ]
io/entrypoints_verification_test: Skip # Test runs in JIT mode only
[ $hot_reload || $hot_reload_rollback || $compiler != dartk && $compiler != dartkb && $compiler != dartkp || $compiler == dartkp && $system == windows ]
io/entrypoints_verification_test: Skip # Cannot run in precompiled Windows because the DLL is linked against dart.exe instead of dart_precompiled_runtime.exe.