[dart2js] Implement await runtime check using kernel transformation.

This replaces the lowering during SSA which requires manually
registering impacts in multiple places. Instead, we use a kernel
transformation to invoke a helper function which implements the
specified semantics.

Change-Id: I58fd11f6d4d1e4f90d00fa826453de024c8686aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/319901
Reviewed-by: Stephen Adams <sra@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Mayank Patke <fishythefish@google.com>
This commit is contained in:
Mayank Patke 2023-08-23 22:58:49 +00:00 committed by Commit Queue
parent 1ba45816e4
commit 5be5d29cd7
17 changed files with 63 additions and 136 deletions

View file

@ -88,10 +88,6 @@ abstract class CommonElements {
/// The `Future` class defined in 'async';.
late final ClassEntity futureClass = _findClass(asyncLibrary, 'Future');
/// The `Future.value` constructor.
late final ConstructorEntity? futureValueConstructor =
_env.lookupConstructor(futureClass, 'value');
/// The `Stream` class defined in 'async';
late final ClassEntity streamClass = _findClass(asyncLibrary, 'Stream');

View file

@ -215,9 +215,6 @@ enum VariableUseKind {
/// A type argument of an generic instantiation.
instantiationTypeArgument,
/// A type variable used in the runtime check type of an `await` expression.
awaitCheck,
}
class VariableUse {
@ -307,9 +304,6 @@ class VariableUse {
static const VariableUse fieldType =
VariableUse._simple(VariableUseKind.fieldType);
static const VariableUse awaitCheck =
VariableUse._simple(VariableUseKind.awaitCheck);
@override
int get hashCode =>
kind.hashCode * 11 +

View file

@ -58,8 +58,6 @@ abstract class ImpactRegistry {
void registerSyncStar(ir.DartType elementType);
void registerAwaitCheck();
void registerAsync(ir.DartType elementType);
void registerAsyncStar(ir.DartType elementType);

View file

@ -4,7 +4,6 @@
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/class_hierarchy.dart' as ir;
import 'package:kernel/core_types.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;
import '../common.dart';
@ -51,8 +50,6 @@ class ImpactBuilder extends StaticTypeVisitor implements ImpactRegistry {
: super(
staticTypeContext.typeEnvironment, classHierarchy, staticTypeCache);
ir.CoreTypes get _coreTypes => typeEnvironment.coreTypes;
@override
VariableScopeModel get variableScopeModel => _variableScopeModel!;
@ -610,24 +607,6 @@ class ImpactBuilder extends StaticTypeVisitor implements ImpactRegistry {
.visitConstant(node.constant);
}
@override
void handleAwaitExpression(ir.AwaitExpression node) {
final runtimeCheckType = node.runtimeCheckType;
if (runtimeCheckType != null) {
// Register the impacts which would have been registered if this were
// implemented directly as Dart code. Some of these may be redundant with
// the impacts we already register for `async` code, but we include them
// for completeness.
registerAwaitCheck();
registerIsCheck(runtimeCheckType);
final typeArgument =
(runtimeCheckType as ir.InterfaceType).typeArguments.single;
registerNew(_coreTypes.futureValueFactory, runtimeCheckType, 1, const [],
[typeArgument], getDeferredImport(node),
isConst: false);
}
}
void _registerFeature(_Feature feature) {
(_data._features ??= EnumSet<_Feature>()).add(feature);
}
@ -902,11 +881,6 @@ class ImpactBuilder extends StaticTypeVisitor implements ImpactRegistry {
_registerTypeUse(elementType, _TypeUseKind.asyncMarker);
}
@override
void registerAwaitCheck() {
_registerFeature(_Feature.awaitCheck);
}
@override
void registerSyncStar(ir.DartType elementType) {
_registerTypeUse(elementType, _TypeUseKind.syncStarMarker);
@ -1421,9 +1395,6 @@ class ImpactData {
case _Feature.intLiteral:
registry.registerIntLiteral();
break;
case _Feature.awaitCheck:
registry.registerAwaitCheck();
break;
}
}
}
@ -1932,7 +1903,6 @@ enum _Feature {
intLiteral,
symbolLiteral,
doubleLiteral,
awaitCheck,
}
class _TypeUse {

View file

@ -839,16 +839,6 @@ class ScopeModelBuilder extends ir.Visitor<EvaluationComplexity>
@override
EvaluationComplexity visitAwaitExpression(ir.AwaitExpression node) {
node.operand = _handleExpression(node.operand);
final runtimeCheckType = node.runtimeCheckType;
if (runtimeCheckType != null) {
visitInContext(runtimeCheckType, VariableUse.awaitCheck);
final typeArgument =
(runtimeCheckType as ir.InterfaceType).typeArguments.single;
visitInContext(
typeArgument,
VariableUse.constructorTypeArgument(
_typeEnvironment.coreTypes.futureValueFactory));
}
return const EvaluationComplexity.lazy();
}

View file

@ -1838,14 +1838,6 @@ abstract class StaticTypeVisitor extends StaticTypeBase {
handleConstantExpression(node);
return super.visitConstantExpression(node);
}
void handleAwaitExpression(ir.AwaitExpression node) {}
@override
ir.DartType visitAwaitExpression(ir.AwaitExpression node) {
handleAwaitExpression(node);
return super.visitAwaitExpression(node);
}
}
class ArgumentTypes {

View file

@ -110,12 +110,6 @@ class BackendImpacts {
],
);
late final BackendImpact awaitExpression = BackendImpact(
staticUses: [
_commonElements.futureValueConstructor!,
],
);
late final BackendImpact asyncBody = BackendImpact(
staticUses: [
_commonElements.asyncHelperAwait,

View file

@ -133,7 +133,6 @@ class BackendUsageBuilder {
bool _isValidEntity(Entity element) {
if (element is ConstructorEntity &&
(element == _commonElements.streamIteratorConstructor ||
element == _commonElements.futureValueConstructor ||
_commonElements.isSymbolConstructor(element))) {
// TODO(johnniwinther): These are valid but we could be more precise.
return true;

View file

@ -226,7 +226,6 @@ class ClosureDataBuilder {
for (VariableUse usage in useSet) {
switch (usage.kind) {
case VariableUseKind.explicit:
case VariableUseKind.awaitCheck:
return true;
case VariableUseKind.implicitCast:
if (_annotationsData

View file

@ -201,11 +201,6 @@ class KernelImpactConverter implements ImpactRegistry {
<DartType>[elementMap.getDartType(elementType)]));
}
@override
void registerAwaitCheck() {
registerBackendImpact(_impacts.awaitExpression);
}
@override
void registerAsync(ir.DartType elementType) {
registerBackendImpact(_impacts.asyncBody);

View file

@ -0,0 +1,34 @@
// Copyright (c) 2023, 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.
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
/// Implements the semantics of `await e`.
///
/// If an expression `e` has a static type of `S`, then `await e` must first
/// check if the runtime type of `e` is `Future<flatten(S)>`. If it is, `e` can
/// be `await`ed directly. Otherwise, we must `await Future.value(e)` (but note
/// that `_Future.value` suffices).
class AwaitLowering {
final CoreTypes _coreTypes;
AwaitLowering(this._coreTypes);
TreeNode transformAwaitExpression(AwaitExpression node) {
final runtimeCheckType = node.runtimeCheckType;
if (runtimeCheckType == null) return node;
// [runtimeCheckType] is guaranteed to be `Future<flatten(S)>`.
final flattenType =
(runtimeCheckType as InterfaceType).typeArguments.single;
final operand = node.operand;
final fileOffset = node.fileOffset;
final helper = _coreTypes.wrapAwaitedExpression;
final arguments = Arguments([operand], types: [flattenType])
..fileOffset = fileOffset;
final wrappedOperand = StaticInvocation(helper, arguments)
..fileOffset = fileOffset;
return AwaitExpression(wrappedOperand)..fileOffset = fileOffset;
}
}

View file

@ -8,6 +8,7 @@ import 'package:kernel/core_types.dart' show CoreTypes;
import '../../options.dart';
import 'async_lowering.dart';
import 'await_lowering.dart';
import 'factory_specializer.dart';
import 'late_lowering.dart';
@ -25,6 +26,7 @@ void transformLibraries(List<Library> libraries, CoreTypes coreTypes,
class _Lowering extends Transformer {
final FactorySpecializer factorySpecializer;
final LateLowering _lateLowering;
final AwaitLowering _awaitLowering;
final AsyncLowering? _asyncLowering;
Member? _currentMember;
@ -33,6 +35,7 @@ class _Lowering extends Transformer {
CoreTypes coreTypes, ClassHierarchy hierarchy, CompilerOptions? _options)
: factorySpecializer = FactorySpecializer(coreTypes, hierarchy),
_lateLowering = LateLowering(coreTypes, _options),
_awaitLowering = AwaitLowering(coreTypes),
_asyncLowering =
(_options?.features.simpleAsyncToFuture.isEnabled ?? false)
? AsyncLowering(coreTypes)
@ -96,7 +99,7 @@ class _Lowering extends Transformer {
TreeNode visitAwaitExpression(AwaitExpression expression) {
_asyncLowering?.visitAwaitExpression(expression);
expression.transformChildren(this);
return expression;
return _awaitLowering.transformAwaitExpression(expression);
}
@override

View file

@ -6060,64 +6060,11 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
@override
void visitAwaitExpression(ir.AwaitExpression node) {
// `await e` first checks if the runtime type of `e` is a subtype of
// `Future<T>` where `T = flatten(S)` and `S` is the static type of `e`.
// (The type `Future<T>` is helpfully precomputed by the CFE and stored in
// [AwaitExpression.runtimeCheckType].)
//
// If the check succeeds (or if no `runtimeCheckType` is provided), we await
// `e` directly.
//
// If the check fails, we must await `Future.value(e)` instead.
//
// See https://github.com/dart-lang/sdk/issues/49396 for details.
final operand = node.operand;
operand.accept(this);
node.operand.accept(this);
HInstruction awaited = pop();
final runtimeCheckType = node.runtimeCheckType;
final sourceInformation = _sourceInformationBuilder.buildAwait(node);
void checkType() {
// TODO(fishythefish): Can we get rid of the redundancy with the type
// checks in async_patch?
_pushIsTest(runtimeCheckType!, awaited, sourceInformation);
}
void pushAwait(HInstruction expression) {
// TODO(herhut): Improve this type.
push(HAwait(expression, _abstractValueDomain.dynamicType)
..sourceInformation = sourceInformation);
}
void awaitUnwrapped() {
pushAwait(awaited);
}
void awaitWrapped() {
final constructor = _commonElements.futureValueConstructor!;
final arguments = [awaited];
// If [runtimeCheckType] exists, it is guaranteed to be `Future<T>`, and
// we want to call `Future<T>.value`.
final typeArgument = _elementMap.getDartType(
(runtimeCheckType as ir.InterfaceType).typeArguments.single);
final typeArguments = [typeArgument];
_addTypeArguments(arguments, typeArguments, sourceInformation);
final instanceType = _commonElements.futureType(typeArgument);
_addImplicitInstantiation(instanceType);
_pushStaticInvocation(constructor, arguments,
_typeInferenceMap.getReturnTypeOf(constructor), typeArguments,
sourceInformation: sourceInformation, instanceType: instanceType);
_removeImplicitInstantiation(instanceType);
pushAwait(pop());
}
if (runtimeCheckType == null) {
awaitUnwrapped();
} else {
SsaBranchBuilder(this)
.handleConditional(checkType, awaitUnwrapped, awaitWrapped);
}
// TODO(herhut): Improve this type.
push(HAwait(awaited, _abstractValueDomain.dynamicType)
..sourceInformation = _sourceInformationBuilder.buildAwait(node));
}
@override

View file

@ -4,10 +4,10 @@ import "dart:async" as asy;
import "dart:core" as core;
static method foo1() → asy::Future<void> async /* futureValueType= void */ {
await 6 /* runtimeCheckType= asy::Future<core::int> */ ;
await asy::_wrapAwaitedExpression<core::int>(6);
}
static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
return (await 6 /* runtimeCheckType= asy::Future<core::int> */ ).{core::num::+}(3){(core::num) → core::int};
return (await asy::_wrapAwaitedExpression<core::int>(6)).{core::num::+}(3){(core::num) → core::int};
}
static method foo3() → asy::Future<void> async /* futureValueType= void */ {
#L1:

View file

@ -4,10 +4,10 @@ import "dart:async" as asy;
import "dart:core" as core;
static method foo1() → asy::Future<void> async /* futureValueType= void */ {
await 6 /* runtimeCheckType= asy::Future<core::int> */ ;
await asy::_wrapAwaitedExpression<core::int>(6);
}
static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
return (await 6 /* runtimeCheckType= asy::Future<core::int> */ ).{core::num::+}(3){(core::num) → core::int};
return (await asy::_wrapAwaitedExpression<core::int>(6)).{core::num::+}(3){(core::num) → core::int};
}
static method foo3() → asy::Future<void> async /* futureValueType= void */ {
#L1:

View file

@ -347,6 +347,9 @@ class CoreTypes {
late final Procedure lateInitializeOnceCheck = index.getTopLevelProcedure(
'dart:_late_helper', '_lateInitializeOnceCheck');
late final Procedure wrapAwaitedExpression =
index.getTopLevelProcedure('dart:async', '_wrapAwaitedExpression');
late final Field enumNameField =
index.getField('dart:core', '_Enum', '_name');

View file

@ -4,7 +4,7 @@
// Patch file for the dart:async library.
import 'dart:_internal' show patch;
import 'dart:_internal' show patch, unsafeCast;
import 'dart:_js_helper'
show
ExceptionAndStackTrace,
@ -734,3 +734,16 @@ class _SyncStarIterable<T> extends Iterable<T> {
_SyncStarIterator<T> get iterator =>
new _SyncStarIterator<T>(JS('', '#()', _outerHelper));
}
/// Wraps an `await`ed expression in [Future.value] if needed.
///
/// If an expression `e` has a static type of `S`, then `await e` must first
/// check if the runtime type of `e` is `Future<flatten(S)>`. If it is, `e` can
/// be `await`ed directly. Otherwise, we must `await Future.value(e)`. Here, [T]
/// is expected to be `flatten(S)`.
///
/// It suffices to use `_Future.value` rather than `Future.value` - see the
/// comments on https://github.com/dart-lang/sdk/issues/50601.
@pragma('dart2js:prefer-inline')
Future<T> _wrapAwaitedExpression<T>(Object? e) =>
e is Future<T> ? e : _Future<T>.value(unsafeCast<T>(e));