[dart2js] Construct _SyncStarIterator outside of transformed code

In the old scheme, sync* generators would have an entry and a
body. The entry contained typed checks and the body had injected code
to call a 'factory' helper method to construct the _SyncStarIterable.
It was necessary to pass the element type to the body to be passed to
the factory.

    int foo(T a, T b) sync* {yield f(a); yield f(b);}

is compiled to something like

    foo(a, b) {
      T._as(a); T._as(b);
      return foo$body(a, b, type$.int);
    }
    foo$body(a, b, R) {
      return _syncStarFactory(function(){BODY}, R);
    }

When type checks were disabled (`-O3`), it was often possible to
generate a single function by merging these as there were no checks.

The new scheme keeps the entry and body separate, and constructs the
_SyncStarIterable in the entry:

    foo(a, b) {
      T._as(a); T._as(b);
      return _syncStarFactory(foo$body(a, b), type$.int);
    }
    foo$body(a, b) {
      return function(){BODY};
    }

This keeps the typed Dart 'level' distinct from the untyped JavaScript
level.

The new scheme is a bit more verbose but has the advantage that the
call to `_syncStarFactory` can be inlined and optimized.

Change-Id: I13802d9c9eefd9323841670d059b75a81569d6cb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/296140
Reviewed-by: Mayank Patke <fishythefish@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
Stephen Adams 2023-04-20 00:38:10 +00:00 committed by Commit Queue
parent 76f76b05bb
commit b3fbb59d60
5 changed files with 85 additions and 37 deletions

View file

@ -1971,11 +1971,6 @@ class SyncStarRewriter extends AsyncRewriterBase {
@override
bool get isSyncStar => true;
/// Constructor creating the Iterable for a sync* method. Called with
/// [bodyName].
final js.Expression iterableFactory;
List<js.Expression>? iterableFactoryTypeArguments;
/// A parameter to the [bodyName] function that passes the controlling
/// `_SyncStarIterator`. This parameter is used to update the state of the
/// iterator.
@ -1991,9 +1986,7 @@ class SyncStarRewriter extends AsyncRewriterBase {
final js.Expression yieldStarSelector;
SyncStarRewriter(DiagnosticReporter diagnosticListener, spannable,
{required this.iterableFactory,
required this.iterableFactoryTypeArguments,
required this.iteratorCurrentValueProperty,
{required this.iteratorCurrentValueProperty,
required this.iteratorDatumProperty,
required this.yieldStarSelector,
required String safeVariableName(String proposedName),
@ -2102,26 +2095,20 @@ class SyncStarRewriter extends AsyncRewriterBase {
"varDecl": variableDeclarations,
"returnInnerInnerFunction": returnInnerInnerFunction,
}).withSourceInformation(functionSourceInformation);
js.Expression callIterableFactory =
js.js("#iterableFactory(#innerFunction, #type)", {
"iterableFactory": iterableFactory,
"type": iterableFactoryTypeArguments,
"innerFunction": innerFunction,
}).withSourceInformation(bodySourceInformation);
js.Statement returnCallIterableFactory = js.Return(callIterableFactory)
.withSourceInformation(bodySourceInformation);
js.Statement returnInnerFunction =
js.Return(innerFunction).withSourceInformation(bodySourceInformation);
return js.js("""
function (#renamedParameters, #typeParameters) {
if (#needsThis)
var #self = this;
#returnCallIterableFactory;
#returnInnerFunction;
}
""", {
"renamedParameters": renamedParameters,
"typeParameters": typeParameters,
"needsThis": analysis.hasThis,
"self": selfName,
"returnCallIterableFactory": returnCallIterableFactory,
"returnInnerFunction": returnInnerFunction,
}).withSourceInformation(functionSourceInformation) as js.Fun;
}
@ -2172,8 +2159,6 @@ class SyncStarRewriter extends AsyncRewriterBase {
@override
void initializeNames() {
iteratorName = freshName('iterator');
iterableFactoryTypeArguments =
processTypeArguments(iterableFactoryTypeArguments);
}
}

View file

@ -122,6 +122,11 @@ class BackendImpacts {
);
late final BackendImpact syncStarBody = BackendImpact(
// The transformed JavaScript code for the sync* body has direct assignments
// to the properties for the instance fields of `_SyncStarIterator`.
// BackendImpact cannot model direct field assigments, so instead the
// impacts are modeled by a call to `_SyncStarIterator._modelGeneratedCode`
// in `moveNext()`.
dynamicUses: [
Selector.fromElement(_commonElements.syncStarIteratorYieldStarMethod),
],

View file

@ -1322,7 +1322,11 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
void _buildFunctionNode(
FunctionEntity function, ir.FunctionNode functionNode) {
if (functionNode.asyncMarker != ir.AsyncMarker.Sync) {
_buildGenerator(function, functionNode);
if (functionNode.asyncMarker == ir.AsyncMarker.SyncStar) {
_buildSyncStarGenerator(function, functionNode);
} else {
_buildGenerator(function, functionNode);
}
return;
}
@ -1430,7 +1434,74 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
_closeFunction();
}
/// Builds an SSA graph for a sync*/async/async* generator body.
/// Builds an SSA graph for a sync* method. A sync* method is split into an
/// entry function and a body function. The entry function calls the body
/// function and wraps the result in an `_SyncStarIterable<T>`. The body
/// function is a separate entity (GeneratorBodyEntity) that is compiled via
/// SSA and the transformed into a reentrant state-machine.
///
/// Here we generate the entry function which is approximately like this:
///
/// Iterable<T> foo(parameters) {
/// return _makeSyncStarIterable<T>(foo$body(parameters));
/// }
void _buildSyncStarGenerator(
FunctionEntity function, ir.FunctionNode functionNode) {
_openFunction(function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function));
// Prepare to call the body generator.
// Is 'buildAsyncBody' the best location for the entry?
var sourceInformation = _sourceInformationBuilder.buildAsyncBody();
// Forward all the parameters to the body.
List<HInstruction> inputs = [];
if (graph.thisInstruction != null) {
inputs.add(graph.thisInstruction!);
}
if (graph.explicitReceiverParameter != null) {
inputs.add(graph.explicitReceiverParameter!);
}
for (Local local in parameters.keys) {
if (!elidedParameters.contains(local)) {
inputs.add(localsHandler.readLocal(local));
}
}
for (Local local in _functionTypeParameterLocals) {
inputs.add(localsHandler.readLocal(local));
}
JGeneratorBody body = _elementMap.getGeneratorBody(function);
push(HInvokeGeneratorBody(
body,
inputs,
_abstractValueDomain.dynamicType, // Untyped JavaScript thunk.
sourceInformation));
// Call `_makeSyncStarIterable<T>(body)`. This usually gets inlined.
final elementType = _elementEnvironment.getAsyncOrSyncStarElementType(
function.asyncMarker, _returnType!);
FunctionEntity method = _commonElements.syncStarIterableFactory;
List<HInstruction> arguments = [pop()];
List<DartType> typeArguments = const [];
if (_rtiNeed.methodNeedsTypeArguments(method)) {
typeArguments = [elementType];
_addTypeArguments(arguments, typeArguments, sourceInformation);
}
_pushStaticInvocation(method, arguments,
_typeInferenceMap.getReturnTypeOf(method), typeArguments,
sourceInformation: sourceInformation);
_closeAndGotoExit(HReturn(_abstractValueDomain, pop(), sourceInformation));
_closeFunction();
}
/// Builds an SSA graph for a async/async* generator body.
void _buildGeneratorBody(
JGeneratorBody function, ir.FunctionNode functionNode) {
FunctionEntity entry = function.function;

View file

@ -246,13 +246,7 @@ class SsaFunctionCompiler implements FunctionCompiler {
js.Expression code,
DartType? asyncTypeParameter,
js.Name name) {
final itemTypeExpression =
_fetchItemTypeNewRti(commonElements, registry, asyncTypeParameter);
SyncStarRewriter rewriter = SyncStarRewriter(_reporter, element,
iterableFactory: emitter
.staticFunctionAccess(commonElements.syncStarIterableFactory),
iterableFactoryTypeArguments: itemTypeExpression,
iteratorCurrentValueProperty: namer.instanceFieldPropertyName(
commonElements.syncStarIteratorCurrentField),
iteratorDatumProperty: namer.instanceFieldPropertyName(
@ -262,11 +256,6 @@ class SsaFunctionCompiler implements FunctionCompiler {
safeVariableName: namer.safeVariablePrefixForAsyncRewrite,
bodyName: namer.deriveAsyncBodyName(name));
registry.registerStaticUse(StaticUse.staticInvoke(
commonElements.syncStarIterableFactory,
CallStructure.unnamed(1, 1),
[elementEnvironment.getFunctionAsyncOrSyncStarElementType(element)]));
return rewriter;
}

View file

@ -49,8 +49,6 @@ void testSyncStarTransform(String source, String expected) {
source,
expected,
SyncStarRewriter(SimpleErrorReporter(), null,
iterableFactory: VariableUse("NewIterable"),
iterableFactoryTypeArguments: [VariableUse("IterableType")],
iteratorCurrentValueProperty: string('_current'),
iteratorDatumProperty: string('_datum'),
yieldStarSelector: string('_yieldStar'),
@ -1295,7 +1293,7 @@ function(a) sync* {
return foo();
}""", """
function(__a) {
return NewIterable(function() {
return function() {
var a = __a;
var __goto = 0, __handler = 2, __currentError;
return function body(__iterator, __errorCode, __result) {
@ -1319,6 +1317,6 @@ function(__a) {
return __iterator._datum = __currentError, 3;
}
};
}, IterableType);
};
}""");
}