mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 17:59:39 +00:00
[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:
parent
1ba45816e4
commit
5be5d29cd7
|
@ -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');
|
||||
|
||||
|
|
|
@ -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 +
|
||||
|
|
|
@ -58,8 +58,6 @@ abstract class ImpactRegistry {
|
|||
|
||||
void registerSyncStar(ir.DartType elementType);
|
||||
|
||||
void registerAwaitCheck();
|
||||
|
||||
void registerAsync(ir.DartType elementType);
|
||||
|
||||
void registerAsyncStar(ir.DartType elementType);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -110,12 +110,6 @@ class BackendImpacts {
|
|||
],
|
||||
);
|
||||
|
||||
late final BackendImpact awaitExpression = BackendImpact(
|
||||
staticUses: [
|
||||
_commonElements.futureValueConstructor!,
|
||||
],
|
||||
);
|
||||
|
||||
late final BackendImpact asyncBody = BackendImpact(
|
||||
staticUses: [
|
||||
_commonElements.asyncHelperAwait,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue