[VM] Add @pragma("vm:non-nullable-result-type") annotation
A field/function annotated with this pragma must be guaranteed to not return `null` at runtime. Make use of this non-nullable annotation in the VM's type propagator. Annotates the "_TypedListView._typedData" field to ensure the VM knows it returns a non-nullable _TypedListView. Furthermore annotates methods on the integer implementation. Those particular methods are recognized methods with a "dynamic" return type. This caused the type propagator to use CompileType::Dynamic() as result type. Since a previous CL started to only utilize the annotated type if it is better than "dynamic" more integer operations got handled in-line, though with null-checks. Annotating those methods to return non-null improves the in-line handling of integer operations. This improves dart-aot On arm7hf: SHA256: +5%, SHA: +6%, JsonObjectRoundTrip: +7%, ... On arm8: SHA1: +28%, MD5: +25%, SHA256: +15%, TypedData.Int16ListViewBench: +18.5%, StringInterpolation: +18%, ... Issue https://github.com/dart-lang/sdk/issues/31954 Issue https://github.com/dart-lang/sdk/issues/35154 Change-Id: Ia4263a37241a36c9dc35e8a48893297effa6f4b2 Reviewed-on: https://dart-review.googlesource.com/c/84421 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Vyacheslav Egorov <vegorov@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
f43c76538b
commit
0ef66e0f5c
@@ -9,6 +9,7 @@ import 'package:kernel/core_types.dart' show CoreTypes;
|
||||
|
||||
const kEntryPointPragmaName = "vm:entry-point";
|
||||
const kExactResultTypePragmaName = "vm:exact-result-type";
|
||||
const kNonNullableResultType = "vm:non-nullable-result-type";
|
||||
|
||||
abstract class ParsedPragma {}
|
||||
|
||||
@@ -29,6 +30,10 @@ class ParsedResultTypeByPathPragma extends ParsedPragma {
|
||||
ParsedResultTypeByPathPragma(this.path);
|
||||
}
|
||||
|
||||
class ParsedNonNullableResultType extends ParsedPragma {
|
||||
ParsedNonNullableResultType();
|
||||
}
|
||||
|
||||
abstract class PragmaAnnotationParser {
|
||||
/// May return 'null' if the annotation does not represent a recognized
|
||||
/// @pragma.
|
||||
@@ -91,6 +96,8 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser {
|
||||
}
|
||||
throw "ERROR: Unsupported option to '$kExactResultTypePragmaName' "
|
||||
"pragma: $options";
|
||||
case kNonNullableResultType:
|
||||
return new ParsedNonNullableResultType();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -159,12 +159,14 @@ class NativeCodeOracle {
|
||||
Type handleNativeProcedure(
|
||||
Member member, EntryPointsListener entryPointsListener) {
|
||||
Type returnType = null;
|
||||
bool nullable = null;
|
||||
|
||||
for (var annotation in member.annotations) {
|
||||
ParsedPragma pragma = _matcher.parsePragma(annotation);
|
||||
if (pragma == null) continue;
|
||||
if (pragma is ParsedResultTypeByTypePragma ||
|
||||
pragma is ParsedResultTypeByPathPragma) {
|
||||
pragma is ParsedResultTypeByPathPragma ||
|
||||
pragma is ParsedNonNullableResultType) {
|
||||
// We can only use the 'vm:exact-result-type' pragma on methods in core
|
||||
// libraries for safety reasons. See 'result_type_pragma.md', detail 1.2
|
||||
// for explanation.
|
||||
@@ -177,7 +179,7 @@ class NativeCodeOracle {
|
||||
var type = pragma.type;
|
||||
if (type is InterfaceType) {
|
||||
returnType = entryPointsListener.addAllocatedClass(type.classNode);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
throw "ERROR: Invalid return type for native method: ${pragma.type}";
|
||||
} else if (pragma is ParsedResultTypeByPathPragma) {
|
||||
@@ -192,16 +194,22 @@ class NativeCodeOracle {
|
||||
// Error is thrown on the next line if the class is not found.
|
||||
Class klass = _libraryIndex.getClass(libName, klassName);
|
||||
Type concreteClass = entryPointsListener.addAllocatedClass(klass);
|
||||
|
||||
returnType = concreteClass;
|
||||
break;
|
||||
} else if (pragma is ParsedNonNullableResultType) {
|
||||
nullable = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (returnType != null && nullable != null) {
|
||||
throw 'ERROR: Cannot have both, @pragma("$kExactResultTypePragmaName") '
|
||||
'and @pragma("$kNonNullableResultType"), annotating the same member.';
|
||||
}
|
||||
|
||||
if (returnType != null) {
|
||||
return returnType;
|
||||
} else {
|
||||
return new Type.fromStatic(member.function.returnType);
|
||||
final coneType = new Type.cone(member.function.returnType);
|
||||
return nullable == false ? coneType : new Type.nullable(coneType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,8 @@ abstract class Type extends TypeExpr {
|
||||
dartType = _normalizeDartType(dartType);
|
||||
if ((dartType == const DynamicType()) || (dartType == const VoidType())) {
|
||||
return const AnyType();
|
||||
} else if (dartType == const BottomType()) {
|
||||
return new Type.empty();
|
||||
} else {
|
||||
return new ConeType(dartType);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# @pragma annotations recognized by the compiler.
|
||||
|
||||
## Annotations for return types and field types.
|
||||
|
||||
The VM is not able to see across method calls (apart from inlining) and
|
||||
therefore does not know anything about the return'ed values of calls, except for
|
||||
the interface type of the signature.
|
||||
|
||||
To improve this we have two types of additional information sources the VM
|
||||
utilizes to gain knowledge about return types:
|
||||
|
||||
- inferred types (stored in kernel metadata): these are computed by global
|
||||
transformations (e.g. TFA) and are only available in AOT mode
|
||||
|
||||
- @pragma annotations: these are recognized in JIT and AOT mode
|
||||
|
||||
This return type information is mainly used in the VM's type propagator.
|
||||
|
||||
Since those annotations side-step the normal type system, they are unsafe and we
|
||||
therefore restrict those annotations to only have an affect inside dart:
|
||||
libraries.
|
||||
|
||||
### @pragma("vm:exact-result-type", <type>) annotation
|
||||
|
||||
Tells the VM about the exact result type (i.e. the exact class-id) of a function
|
||||
or a field load.
|
||||
|
||||
There are two limitations on this pragma:
|
||||
|
||||
0. The Dart object returned by the method at runtime must have **exactly** the type specified in the annotation (not a subtype).
|
||||
|
||||
1. The exact return type declared in the pragma must be a subtype of the interface type declared in the method signature.
|
||||
Note that this restriction is not enforced automatically by the compiler.
|
||||
|
||||
If those limitations are violated, undefined behavior may result.
|
||||
Note that since `null` is an instance of the `Null` type, which is a subtype of any other, exactness of the annotated result type implies that the result must be non-null.
|
||||
|
||||
#### Syntax
|
||||
|
||||
```dart
|
||||
class A {}
|
||||
class B extends A {}
|
||||
|
||||
// Reference to type via type literal
|
||||
@pragma("vm:exact-result-type", B)
|
||||
A foo() native "foo_impl";
|
||||
|
||||
// Reference to type via path
|
||||
@pragma("vm:exact-result-type", "dart:core#_Smi");
|
||||
int foo() native "foo_impl";
|
||||
|
||||
class C {
|
||||
// Reference to type via type literal
|
||||
@pragma('vm:exact-result-type', B)
|
||||
final B bValue;
|
||||
|
||||
// Reference to type via path
|
||||
@pragma('vm:exact-result-type', "dart:core#_Smi")
|
||||
final int intValue;
|
||||
}
|
||||
```
|
||||
|
||||
### @pragma("vm:non-nullable-result-type") annotation
|
||||
|
||||
Tells the VM that the method/field cannot return `null`.
|
||||
|
||||
There is one limitation on this pragma:
|
||||
|
||||
0. The Dart object returned by the method at runtime **must not** return `null`.
|
||||
|
||||
If this limitation is violated, undefined behavior may result.
|
||||
|
||||
#### Syntax
|
||||
|
||||
```dart
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
A foo() native "foo_impl";
|
||||
|
||||
class C {
|
||||
@pragma('vm:non-nullable-result-type");
|
||||
final int value;
|
||||
}
|
||||
```
|
||||
@@ -1,56 +0,0 @@
|
||||
# `vm:exact-result-type` pragma
|
||||
|
||||
To facilitate type-flow analysis and other optimizations, Dart methods may use
|
||||
the pragma `vm:exact-result-type` to declare an exact return type different than
|
||||
the return type in the signature of the method. There are three limitations on
|
||||
this pragma:
|
||||
|
||||
Similarly if a field is marked with the same annotation it must be guaranteed
|
||||
that a load from the field returns in the specified exact result type.
|
||||
|
||||
0. The Dart object returned by the method at runtime must have exactly the type
|
||||
specified in the annotation (not a subtype).
|
||||
|
||||
1. The exact return type declared in the pragma must be a subtype of the return
|
||||
type declared in the method signature.
|
||||
Note that this restriction is not enforced automatically by the compiler.
|
||||
|
||||
2. `vm:exact-result-type` may only be attached to methods in the `dart:*`
|
||||
libraries.
|
||||
This pragma can introduce unsafe behavior since it allows the compiler to
|
||||
make stronger assumptions during optimization than what the sound strong-mode
|
||||
type system allows, so it is only allowed in the core library where the Dart
|
||||
VM team can ensure that it is not misused.
|
||||
|
||||
If limitations 0 or 1 are violated, undefined behavior may result.
|
||||
Note that since `null` is an instance of the `Null` type, which is a subtype of any other, exactness of the annotated result type implies that the result must be non-null.
|
||||
|
||||
## Syntax
|
||||
|
||||
### Reference to type via type literal
|
||||
|
||||
```dart
|
||||
class A {}
|
||||
class B extends A {}
|
||||
|
||||
@pragma('vm:exact-result-type', B)
|
||||
A foo() native 'foo_impl';
|
||||
|
||||
class C {
|
||||
@pragma('vm:exact-result-type', int)
|
||||
final int value;
|
||||
}
|
||||
```
|
||||
|
||||
### Reference to type via path
|
||||
|
||||
```dart
|
||||
@pragma('vm:exact-result-type', 'dart:core#_Smi');
|
||||
int foo() native 'foo_impl';
|
||||
|
||||
class C {
|
||||
@pragma('vm:exact-result-type', 'dart:core#_Smi')
|
||||
final int value;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -40,6 +40,7 @@ class _Double implements double {
|
||||
return _trunc_div(other.toDouble());
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _trunc_div(double other) native "Double_trunc_div";
|
||||
|
||||
@pragma("vm:exact-result-type", _Double)
|
||||
@@ -71,7 +72,9 @@ class _Double implements double {
|
||||
return (other is num) && _equal(other.toDouble());
|
||||
}
|
||||
|
||||
@pragma("vm:exact-result-type", bool)
|
||||
bool _equal(double other) native "Double_equal";
|
||||
@pragma("vm:exact-result-type", bool)
|
||||
bool _equalToInteger(int other) native "Double_equalToInteger";
|
||||
|
||||
@pragma("vm:exact-result-type", bool)
|
||||
@@ -84,6 +87,7 @@ class _Double implements double {
|
||||
return _greaterThan(other.toDouble());
|
||||
}
|
||||
|
||||
@pragma("vm:exact-result-type", bool)
|
||||
bool _greaterThan(double other) native "Double_greaterThan";
|
||||
|
||||
@pragma("vm:exact-result-type", bool)
|
||||
@@ -175,6 +179,7 @@ class _Double implements double {
|
||||
return this;
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int toInt() native "Double_toInt";
|
||||
|
||||
double toDouble() {
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
// part of "core_patch.dart";
|
||||
|
||||
abstract class _IntegerImplementation implements int {
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
num operator +(num other) => other._addFromInteger(this);
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
num operator -(num other) => other._subFromInteger(this);
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
num operator *(num other) => other._mulFromInteger(this);
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator ~/(num other) {
|
||||
if ((other is int) && (other == 0)) {
|
||||
throw const IntegerDivisionByZeroException();
|
||||
@@ -20,6 +24,7 @@ abstract class _IntegerImplementation implements int {
|
||||
return this.toDouble() / other.toDouble();
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
num operator %(num other) {
|
||||
if ((other is int) && (other == 0)) {
|
||||
throw const IntegerDivisionByZeroException();
|
||||
@@ -27,34 +32,51 @@ abstract class _IntegerImplementation implements int {
|
||||
return other._moduloFromInteger(this);
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator -() {
|
||||
return 0 - this;
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator &(int other) => other._bitAndFromInteger(this);
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator |(int other) => other._bitOrFromInteger(this);
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator ^(int other) => other._bitXorFromInteger(this);
|
||||
|
||||
num remainder(num other) {
|
||||
return other._remainderFromInteger(this);
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _bitAndFromSmi(_Smi other) native "Integer_bitAndFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _bitAndFromInteger(int other) native "Integer_bitAndFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _bitOrFromInteger(int other) native "Integer_bitOrFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _bitXorFromInteger(int other) native "Integer_bitXorFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _shrFromInteger(int other) native "Integer_shrFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _shlFromInteger(int other) native "Integer_shlFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _addFromInteger(int other) native "Integer_addFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _subFromInteger(int other) native "Integer_subFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _mulFromInteger(int other) native "Integer_mulFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _truncDivFromInteger(int other) native "Integer_truncDivFromInteger";
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int _moduloFromInteger(int other) native "Integer_moduloFromInteger";
|
||||
int _remainderFromInteger(int other) {
|
||||
return other - (other ~/ this) * this;
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator >>(int other) => other._shrFromInteger(this);
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator <<(int other) => other._shlFromInteger(this);
|
||||
|
||||
@pragma("vm:exact-result-type", bool)
|
||||
@@ -664,7 +686,9 @@ class _Mint extends _IntegerImplementation {
|
||||
}
|
||||
int get hashCode => this;
|
||||
int get _identityHashCode => this;
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
int operator ~() native "Mint_bitNegate";
|
||||
@pragma("vm:exact-result-type", "dart:core#_Smi")
|
||||
int get bitLength native "Mint_bitLength";
|
||||
|
||||
int _bitAndFromSmi(_Smi other) => _bitAndFromInteger(other);
|
||||
|
||||
@@ -3577,6 +3577,7 @@ abstract class _TypedListView extends _TypedListBase implements TypedData {
|
||||
return _typedData.buffer;
|
||||
}
|
||||
|
||||
@pragma("vm:non-nullable-result-type")
|
||||
final _TypedList _typedData;
|
||||
|
||||
@pragma("vm:exact-result-type", "dart:core#_Smi")
|
||||
|
||||
@@ -131,6 +131,8 @@ const Slot& Slot::Get(const Field& field,
|
||||
if (cid != kDynamicCid) {
|
||||
nullable_cid = cid;
|
||||
is_nullable = false;
|
||||
} else if (MethodRecognizer::HasNonNullableResultTypeFromPragma(field)) {
|
||||
is_nullable = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1182,12 +1182,15 @@ CompileType InstanceCallInstr::ComputeType() const {
|
||||
}
|
||||
|
||||
CompileType PolymorphicInstanceCallInstr::ComputeType() const {
|
||||
bool is_nullable = CompileType::kNullable;
|
||||
if (IsSureToCallSingleRecognizedTarget()) {
|
||||
const Function& target = *targets_.TargetAt(0)->target;
|
||||
if (target.has_pragma()) {
|
||||
const intptr_t cid = MethodRecognizer::ResultCidFromPragma(target);
|
||||
if (cid != kDynamicCid) {
|
||||
return CompileType::FromCid(cid);
|
||||
} else if (MethodRecognizer::HasNonNullableResultTypeFromPragma(target)) {
|
||||
is_nullable = CompileType::kNonNullable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1195,7 +1198,7 @@ CompileType PolymorphicInstanceCallInstr::ComputeType() const {
|
||||
if (Isolate::Current()->can_use_strong_mode_types()) {
|
||||
CompileType* type = instance_call()->Type();
|
||||
TraceStrongModeType(this, type);
|
||||
return *type;
|
||||
return is_nullable ? *type : type->CopyNonNullable();
|
||||
}
|
||||
|
||||
return CompileType::Dynamic();
|
||||
@@ -1210,11 +1213,15 @@ CompileType StaticCallInstr::ComputeType() const {
|
||||
return *inferred_type;
|
||||
}
|
||||
|
||||
bool is_nullable = CompileType::kNullable;
|
||||
if (function_.has_pragma()) {
|
||||
const intptr_t cid = MethodRecognizer::ResultCidFromPragma(function_);
|
||||
if (cid != kDynamicCid) {
|
||||
return CompileType::FromCid(cid);
|
||||
}
|
||||
if (MethodRecognizer::HasNonNullableResultTypeFromPragma(function_)) {
|
||||
is_nullable = CompileType::kNonNullable;
|
||||
}
|
||||
}
|
||||
|
||||
if (Isolate::Current()->can_use_strong_mode_types()) {
|
||||
@@ -1225,8 +1232,8 @@ CompileType StaticCallInstr::ComputeType() const {
|
||||
// non-instantiated types properly.
|
||||
if (result_type.IsInstantiated()) {
|
||||
TraceStrongModeType(this, result_type);
|
||||
const bool is_nullable =
|
||||
(inferred_type == NULL) || inferred_type->is_nullable();
|
||||
is_nullable = is_nullable &&
|
||||
(inferred_type == nullptr || inferred_type->is_nullable());
|
||||
return CompileType::FromAbstractType(result_type, is_nullable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,34 @@ intptr_t MethodRecognizer::ResultCidFromPragma(
|
||||
return kDynamicCid;
|
||||
}
|
||||
|
||||
bool MethodRecognizer::HasNonNullableResultTypeFromPragma(
|
||||
const Object& function_or_field) {
|
||||
auto T = Thread::Current();
|
||||
auto Z = T->zone();
|
||||
auto& klass = Class::Handle(Z);
|
||||
if (function_or_field.IsFunction()) {
|
||||
auto& function = Function::Cast(function_or_field);
|
||||
ASSERT(function.has_pragma());
|
||||
klass = function.Owner();
|
||||
} else {
|
||||
auto& field = Field::Cast(function_or_field);
|
||||
ASSERT(field.has_pragma());
|
||||
klass = field.Owner();
|
||||
}
|
||||
auto& library = Library::Handle(Z, klass.library());
|
||||
const bool can_use_pragma = library.IsAnyCoreLibrary();
|
||||
if (can_use_pragma) {
|
||||
auto& option = Object::Handle(Z);
|
||||
if (library.FindPragma(T, function_or_field,
|
||||
Symbols::vm_non_nullable_result_type(), &option)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing said otherwise, the return type is nullable.
|
||||
return false;
|
||||
}
|
||||
|
||||
intptr_t MethodRecognizer::MethodKindToReceiverCid(Kind kind) {
|
||||
switch (kind) {
|
||||
case kImmutableArrayGetIndexed:
|
||||
|
||||
@@ -535,6 +535,14 @@ class MethodRecognizer : public AllStatic {
|
||||
// See [result_type_pragma.md].
|
||||
static intptr_t ResultCidFromPragma(const Object& function_or_field);
|
||||
|
||||
// Try to find an annotation of the form
|
||||
// @pragma("vm:non-nullable-result-type")
|
||||
// and returns true iff `false` was specified in the annotation.
|
||||
//
|
||||
// See [pragmas.md].
|
||||
static bool HasNonNullableResultTypeFromPragma(
|
||||
const Object& function_or_field);
|
||||
|
||||
static intptr_t MethodKindToReceiverCid(Kind kind);
|
||||
static const char* KindToCString(Kind kind);
|
||||
|
||||
|
||||
@@ -456,6 +456,7 @@ class ObjectPointerVisitor;
|
||||
V(DebugProcedureName, ":Eval") \
|
||||
V(DebugClassName, "#DebugClass") \
|
||||
V(vm_entry_point, "vm:entry-point") \
|
||||
V(vm_non_nullable_result_type, "vm:non-nullable-result-type") \
|
||||
V(vm_exact_result_type, "vm:exact-result-type") \
|
||||
V(Get, "get") \
|
||||
V(Set, "set") \
|
||||
|
||||
Reference in New Issue
Block a user