[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:
Martin Kustermann
2018-11-22 16:18:27 +00:00
committed by commit-bot@chromium.org
parent f43c76538b
commit 0ef66e0f5c
13 changed files with 184 additions and 64 deletions
+7
View File
@@ -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;
}
```
+5
View File
@@ -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() {
+24
View File
@@ -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);
+1
View File
@@ -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")
+2
View File
@@ -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;
}
}
+10 -3
View File
@@ -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);
}
}
+28
View File
@@ -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:
+8
View File
@@ -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);
+1
View File
@@ -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") \