Skip to content

Commit

Permalink
[VM] Add @pragma("vm:non-nullable-result-type") annotation
Browse files Browse the repository at this point in the history
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 #31954
Issue #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>
  • Loading branch information
mkustermann authored and commit-bot@chromium.org committed Nov 22, 2018
1 parent f43c765 commit 0ef66e0
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 64 deletions.
7 changes: 7 additions & 0 deletions pkg/vm/lib/transformations/pragma.dart
Expand Up @@ -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 {}

Expand All @@ -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.
Expand Down Expand Up @@ -91,6 +96,8 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser {
}
throw "ERROR: Unsupported option to '$kExactResultTypePragmaName' "
"pragma: $options";
case kNonNullableResultType:
return new ParsedNonNullableResultType();
default:
return null;
}
Expand Down
18 changes: 13 additions & 5 deletions pkg/vm/lib/transformations/type_flow/native_code.dart
Expand Up @@ -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.
Expand All @@ -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) {
Expand All @@ -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);
}
}
}
2 changes: 2 additions & 0 deletions pkg/vm/lib/transformations/type_flow/types.dart
Expand Up @@ -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);
}
Expand Down
83 changes: 83 additions & 0 deletions runtime/docs/compiler/pragmas_recognized_by_compiler.md
@@ -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;
}
```
56 changes: 0 additions & 56 deletions runtime/docs/compiler/result_type_pragma.md

This file was deleted.

5 changes: 5 additions & 0 deletions runtime/lib/double.dart
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -175,6 +179,7 @@ class _Double implements double {
return this;
}

@pragma("vm:non-nullable-result-type")
int toInt() native "Double_toInt";

double toDouble() {
Expand Down
24 changes: 24 additions & 0 deletions runtime/lib/integers.dart
Expand Up @@ -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();
Expand All @@ -20,41 +24,59 @@ 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();
}
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)
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions runtime/lib/typed_data_patch.dart
Expand Up @@ -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")
Expand Down
2 changes: 2 additions & 0 deletions runtime/vm/compiler/backend/slot.cc
Expand Up @@ -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;
}
}

Expand Down

0 comments on commit 0ef66e0

Please sign in to comment.