1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-05 09:20:04 +00:00

[dart2wasm] Move yield finder to a shared library

- Generalize `YieldFinder` to consider both `await` and `yield` as a
  suspension point.

- Move it to `state_machine.dart`, reuse it in async and sync* code
  generators.

This is a change from
https://dart-review.googlesource.com/c/sdk/+/366663, moved to a spearate
CL to make revieweing easier.

The end goal is to share code generation for async and sync* code
generators.

Change-Id: I4c75c746f85b2fedf7c1117a20fcd32152573c6d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/367021
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This commit is contained in:
Ömer Sinan Ağacan 2024-05-21 09:09:40 +00:00 committed by Commit Queue
parent 035be2b0d4
commit 3863e78e80
3 changed files with 222 additions and 330 deletions

View File

@ -8,180 +8,7 @@ import 'package:wasm_builder/wasm_builder.dart' as w;
import 'class_info.dart';
import 'closures.dart';
import 'code_generator.dart';
import 'sync_star.dart' show StateTarget, StateTargetPlacement;
/// Identify which statements contain `await` statements, and assign target
/// indices to all control flow targets of these.
///
/// Target indices are assigned in program order.
class _YieldFinder extends RecursiveVisitor {
final List<StateTarget> targets = [];
final bool enableAsserts;
// The number of `await` statements seen so far.
int yieldCount = 0;
_YieldFinder(this.enableAsserts);
List<StateTarget> find(FunctionNode function) {
// Initial state
addTarget(function.body!, StateTargetPlacement.Inner);
assert(function.body is Block || function.body is ReturnStatement);
recurse(function.body!);
// Final state
addTarget(function.body!, StateTargetPlacement.After);
return targets;
}
/// Recurse into a statement and then remove any targets added by the
/// statement if it doesn't contain any `await` statements.
void recurse(Statement statement) {
final yieldCountIn = yieldCount;
final targetsIn = targets.length;
statement.accept(this);
if (yieldCount == yieldCountIn) {
targets.length = targetsIn;
}
}
void addTarget(TreeNode node, StateTargetPlacement placement) {
targets.add(StateTarget(targets.length, node, placement));
}
@override
void visitBlock(Block node) {
for (Statement statement in node.statements) {
recurse(statement);
}
}
@override
void visitDoStatement(DoStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
}
@override
void visitForStatement(ForStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitIfStatement(IfStatement node) {
recurse(node.then);
if (node.otherwise != null) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.otherwise!);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitLabeledStatement(LabeledStatement node) {
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitSwitchStatement(SwitchStatement node) {
for (SwitchCase c in node.cases) {
addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryFinally(TryFinally node) {
// [TryFinally] blocks are always compiled to as CFG, even when they don't
// have awaits. This is to keep the code size small: with normal
// compilation finalizer blocks need to be duplicated based on
// continuations, which we don't need in the CFG implementation.
yieldCount += 1;
recurse(node.body);
addTarget(node, StateTargetPlacement.Inner);
recurse(node.finalizer);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryCatch(TryCatch node) {
// Also always compile [TryCatch] blocks to the CFG to be able to set
// finalizer continuations.
yieldCount += 1;
recurse(node.body);
for (Catch c in node.catches) {
addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitWhileStatement(WhileStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitYieldStatement(YieldStatement node) {
throw 'Yield statement in async function: $node (${node.location})';
}
// Handle awaits. After the await transformation await can only appear in a
// RHS of a top-level assignment, or as a top-level statement.
@override
void visitVariableSet(VariableSet node) {
if (node.value is AwaitExpression) {
yieldCount++;
addTarget(node, StateTargetPlacement.After);
} else {
super.visitVariableSet(node);
}
}
@override
void visitExpressionStatement(ExpressionStatement node) {
if (node.expression is AwaitExpression) {
yieldCount++;
addTarget(node, StateTargetPlacement.After);
} else {
super.visitExpressionStatement(node);
}
}
@override
void visitFunctionExpression(FunctionExpression node) {}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {}
// Any other await expression means the await transformer is buggy and didn't
// transform the expression as expected.
@override
void visitAwaitExpression(AwaitExpression node) {
// Await expressions should've been converted to `VariableSet` statements
// by `_AwaitTransformer`.
throw 'Unexpected await expression: $node (${node.location})';
}
@override
void visitAssertStatement(AssertStatement node) {
if (enableAsserts) {
super.visitAssertStatement(node);
}
}
@override
void visitAssertBlock(AssertBlock node) {
if (enableAsserts) {
super.visitAssertBlock(node);
}
}
}
import 'state_machine.dart';
class _ExceptionHandlerStack {
/// Current exception handler stack. A CFG block generated when this is not
@ -593,7 +420,7 @@ class AsyncCodeGenerator extends CodeGenerator {
void _generateBodies(FunctionNode functionNode) {
// Number and categorize CFG targets.
targets = _YieldFinder(translator.options.enableAsserts).find(functionNode);
targets = YieldFinder(translator.options.enableAsserts).find(functionNode);
for (final target in targets) {
switch (target.placement) {
case StateTargetPlacement.Inner:
@ -1344,7 +1171,7 @@ class AsyncCodeGenerator extends CodeGenerator {
}
// Set state target to label after await.
final StateTarget after = afterTargets[node.parent]!;
final StateTarget after = afterTargets[node]!;
b.local_get(suspendStateLocal);
b.i32_const(after.index);
b.struct_set(

View File

@ -0,0 +1,213 @@
// Copyright (c) 2024, 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';
/// Placement of a control flow graph target within a statement. This
/// distinction is necessary since some statements need to have two targets
/// associated with them.
///
/// The meanings of the variants are:
///
/// - [Inner]: Loop entry of a [DoStatement], condition of a [ForStatement] or
/// [WhileStatement], the `else` branch of an [IfStatement], or the
/// initial entry target for a function body.
///
/// - [After]: After a statement, the resumption point of a suspension point
/// ([YieldStatement] or [AwaitExpression]), or the final state
/// (iterator done) of a function body.
enum StateTargetPlacement { Inner, After }
/// Representation of target in the `sync*` control flow graph.
class StateTarget {
final int index;
final TreeNode node;
final StateTargetPlacement placement;
StateTarget(this.index, this.node, this.placement);
@override
String toString() {
String place = placement == StateTargetPlacement.Inner ? "in" : "after";
return "$index: $place $node";
}
}
/// Identify which statements contain `await` or `yield` statements, and assign
/// target indices to all control flow targets of these.
///
/// Target indices are assigned in program order.
class YieldFinder extends RecursiveVisitor {
final List<StateTarget> targets = [];
final bool enableAsserts;
// The number of `await` statements seen so far.
int yieldCount = 0;
YieldFinder(this.enableAsserts);
List<StateTarget> find(FunctionNode function) {
// Initial state
addTarget(function.body!, StateTargetPlacement.Inner);
assert(function.body is Block || function.body is ReturnStatement);
recurse(function.body!);
// Final state
addTarget(function.body!, StateTargetPlacement.After);
return targets;
}
/// Recurse into a statement and then remove any targets added by the
/// statement if it doesn't contain any `await` statements.
void recurse(Statement statement) {
final yieldCountIn = yieldCount;
final targetsIn = targets.length;
statement.accept(this);
if (yieldCount == yieldCountIn) {
targets.length = targetsIn;
}
}
void addTarget(TreeNode node, StateTargetPlacement placement) {
targets.add(StateTarget(targets.length, node, placement));
}
@override
void visitBlock(Block node) {
for (Statement statement in node.statements) {
recurse(statement);
}
}
@override
void visitDoStatement(DoStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
}
@override
void visitForStatement(ForStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitIfStatement(IfStatement node) {
recurse(node.then);
if (node.otherwise != null) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.otherwise!);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitLabeledStatement(LabeledStatement node) {
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitSwitchStatement(SwitchStatement node) {
for (SwitchCase c in node.cases) {
addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryFinally(TryFinally node) {
// [TryFinally] blocks are always compiled to as CFG, even when they don't
// have awaits. This is to keep the code size small: with normal
// compilation finalizer blocks need to be duplicated based on
// continuations, which we don't need in the CFG implementation.
yieldCount++;
recurse(node.body);
addTarget(node, StateTargetPlacement.Inner);
recurse(node.finalizer);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryCatch(TryCatch node) {
// Also always compile [TryCatch] blocks to the CFG to be able to set
// finalizer continuations.
yieldCount++;
recurse(node.body);
for (Catch c in node.catches) {
addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitWhileStatement(WhileStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitYieldStatement(YieldStatement node) {
yieldCount++;
addTarget(node, StateTargetPlacement.After);
}
// Handle awaits. After the await transformation await can only appear in a
// RHS of a top-level assignment, or as a top-level statement.
@override
void visitExpressionStatement(ExpressionStatement node) {
final expression = node.expression;
// Handle awaits in RHS of assignments.
if (expression is VariableSet) {
final value = expression.value;
if (value is AwaitExpression) {
yieldCount++;
addTarget(value, StateTargetPlacement.After);
return;
}
}
// Handle top-level awaits.
if (expression is AwaitExpression) {
yieldCount++;
addTarget(node, StateTargetPlacement.After);
return;
}
super.visitExpressionStatement(node);
}
@override
void visitFunctionExpression(FunctionExpression node) {}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {}
// Any other await expression means the await transformer is buggy and didn't
// transform the expression as expected.
@override
void visitAwaitExpression(AwaitExpression node) {
// Await expressions should've been converted to `VariableSet` statements
// by `_AwaitTransformer`.
throw 'Unexpected await expression: $node (${node.location})';
}
@override
void visitAssertStatement(AssertStatement node) {
if (enableAsserts) {
super.visitAssertStatement(node);
}
}
@override
void visitAssertBlock(AssertBlock node) {
if (enableAsserts) {
super.visitAssertBlock(node);
}
}
}

View File

@ -8,155 +8,7 @@ import 'package:wasm_builder/wasm_builder.dart' as w;
import 'class_info.dart';
import 'closures.dart';
import 'code_generator.dart';
/// Placement of a control flow graph target within a statement. This
/// distinction is necessary since some statements need to have two targets
/// associated with them.
///
/// The meanings of the variants are:
///
/// - [Inner]: Loop entry of a [DoStatement], condition of a [ForStatement] or
/// [WhileStatement], the `else` branch of an [IfStatement], or the
/// initial entry target for a function body.
/// - [After]: After a statement, the resumption point of a [YieldStatement],
/// or the final state (iterator done) of a function body.
enum StateTargetPlacement { Inner, After }
/// Representation of target in the `sync*` control flow graph.
class StateTarget {
int index;
TreeNode node;
StateTargetPlacement placement;
StateTarget(this.index, this.node, this.placement);
@override
String toString() {
String place = placement == StateTargetPlacement.Inner ? "in" : "after";
return "$index: $place $node";
}
}
/// Identify which statements contain `yield` or `yield*` statements, and assign
/// target indices to all control flow targets of these.
///
/// Target indices are assigned in program order.
class _YieldFinder extends StatementVisitor<void>
with StatementVisitorDefaultMixin<void> {
final SyncStarCodeGenerator codeGen;
// The number of `yield` or `yield*` statements seen so far.
int yieldCount = 0;
_YieldFinder(this.codeGen);
List<StateTarget> get targets => codeGen.targets;
void find(FunctionNode function) {
// Initial state
addTarget(function.body!, StateTargetPlacement.Inner);
assert(function.body is Block || function.body is ReturnStatement);
recurse(function.body!);
// Final state
addTarget(function.body!, StateTargetPlacement.After);
}
/// Recurse into a statement and then remove any targets added by the
/// statement if it doesn't contain any `yield` or `yield*` statements.
void recurse(Statement statement) {
int yieldCountIn = yieldCount;
int targetsIn = targets.length;
statement.accept(this);
if (yieldCount == yieldCountIn) targets.length = targetsIn;
}
void addTarget(TreeNode node, StateTargetPlacement placement) {
targets.add(StateTarget(targets.length, node, placement));
}
@override
void defaultStatement(Statement node) {
// Statements not explicitly handled in this visitor can never contain any
// `yield` or `yield*` statements. It is assumed that this holds for all
// [BlockExpression]s in the function.
}
@override
void visitBlock(Block node) {
for (Statement statement in node.statements) {
recurse(statement);
}
}
@override
void visitDoStatement(DoStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
}
@override
void visitForStatement(ForStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitIfStatement(IfStatement node) {
recurse(node.then);
if (node.otherwise != null) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.otherwise!);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitLabeledStatement(LabeledStatement node) {
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitSwitchStatement(SwitchStatement node) {
for (SwitchCase c in node.cases) {
addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryCatch(TryCatch node) {
recurse(node.body);
for (Catch c in node.catches) {
addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryFinally(TryFinally node) {
recurse(node.body);
addTarget(node, StateTargetPlacement.Inner);
recurse(node.finalizer);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitWhileStatement(WhileStatement node) {
addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
addTarget(node, StateTargetPlacement.After);
}
@override
void visitYieldStatement(YieldStatement node) {
yieldCount++;
addTarget(node, StateTargetPlacement.After);
}
}
import 'state_machine.dart';
/// A specialized code generator for generating code for `sync*` functions.
///
@ -177,7 +29,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
SyncStarCodeGenerator(super.translator, super.function, super.reference);
/// Targets of the CFG, indexed by target index.
final List<StateTarget> targets = [];
late final List<StateTarget> targets;
// Targets categorized by placement and indexed by node.
final Map<TreeNode, StateTarget> innerTargets = {};
@ -209,20 +61,20 @@ class SyncStarCodeGenerator extends CodeGenerator {
void generate() {
closures = Closures(translator, member);
setupParametersAndContexts(member.reference);
generateBodies(member.function!);
_generateBodies(member.function!);
}
@override
w.BaseFunction generateLambda(Lambda lambda, Closures closures) {
this.closures = closures;
setupLambdaParametersAndContexts(lambda);
generateBodies(lambda.functionNode);
_generateBodies(lambda.functionNode);
return function;
}
void generateBodies(FunctionNode functionNode) {
void _generateBodies(FunctionNode functionNode) {
// Number and categorize CFG targets.
_YieldFinder(this).find(functionNode);
targets = YieldFinder(translator.options.enableAsserts).find(functionNode);
for (final target in targets) {
switch (target.placement) {
case StateTargetPlacement.Inner: