Constant propagation
Issue: https://github.com/dart-lang/sdk/issues/61635 Change-Id: I105cf5914cad3db83f3d8097ddf793be454e43d6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/465741 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
02641c88d0
commit
45ec012c66
@@ -692,6 +692,31 @@ class AstToIr extends ast.RecursiveVisitor {
|
||||
});
|
||||
}
|
||||
|
||||
void _generateSwitchComparison(
|
||||
Definition value,
|
||||
ast.Expression caseExpression,
|
||||
) {
|
||||
_translateNode(caseExpression);
|
||||
// TODO(alexmarkov): use proper devirtualization to specialize ==.
|
||||
final interfaceTarget = (builder.stackTop.type is IntType)
|
||||
? coreTypes.index.getProcedure('dart:core', 'num', '==')
|
||||
: coreTypes.objectEquals;
|
||||
builder.push(value);
|
||||
final matcher = recognizedMethods.instanceInvocations[interfaceTarget];
|
||||
if (matcher != null) {
|
||||
final snippet = matcher.match([_staticType(caseExpression), value.type]);
|
||||
if (snippet != null) {
|
||||
snippet(builder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
builder.addInterfaceCall(
|
||||
functionRegistry.getFunction(interfaceTarget),
|
||||
2,
|
||||
const BoolType(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSwitchStatement(ast.SwitchStatement node) {
|
||||
_translateNode(node.expression);
|
||||
@@ -717,14 +742,7 @@ class AstToIr extends ast.RecursiveVisitor {
|
||||
builder.currentSourcePosition = SourcePosition(
|
||||
switchCase.expressionOffsets[i],
|
||||
);
|
||||
// TODO(alexmarkov): use more efficient comparison
|
||||
_translateNode(switchCase.expressions[i]);
|
||||
builder.push(value);
|
||||
builder.addInterfaceCall(
|
||||
functionRegistry.getFunction(coreTypes.objectEquals),
|
||||
2,
|
||||
const BoolType(),
|
||||
);
|
||||
_generateSwitchComparison(value, switchCase.expressions[i]);
|
||||
|
||||
final trueBlock = builder.newTargetBlock();
|
||||
final falseBlock = builder.newTargetBlock();
|
||||
|
||||
@@ -0,0 +1,569 @@
|
||||
// Copyright (c) 2025, 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:cfg/ir/constant_value.dart';
|
||||
import 'package:cfg/ir/instructions.dart';
|
||||
import 'package:cfg/ir/visitor.dart';
|
||||
import 'package:cfg/passes/pass.dart';
|
||||
import 'package:cfg/utils/bit_vector.dart';
|
||||
|
||||
/// Sparse conditional constant propagation.
|
||||
///
|
||||
/// Performs constant propagation and unreachable code elimination
|
||||
/// at the same time.
|
||||
///
|
||||
/// The algorithm is described in Wegman, Mark N. and Zadeck, F. Kenneth.
|
||||
/// "Constant Propagation with Conditional Branches".
|
||||
final class ConstantPropagation extends Pass
|
||||
implements InstructionVisitor<void> {
|
||||
final ConstantFolding constantFolding = ConstantFolding();
|
||||
|
||||
// State transition for blocks: unreachable -> reachable.
|
||||
late final BitVector _reachable = BitVector(graph.preorder.length);
|
||||
|
||||
// State transitions for definitions:
|
||||
// unknown -> constant -> non-constant
|
||||
final Map<Definition, ConstantValue> _constantValues = {};
|
||||
late final BitVector _nonConstant = BitVector(graph.instructions.length);
|
||||
|
||||
// State transitions for phis:
|
||||
// unknown -> redundant -> non-redundant
|
||||
final Map<Phi, Definition> _redundantPhis = {};
|
||||
|
||||
// Work lists for blocks and definitions, appended on state transitions.
|
||||
final _blockWorkList = <Block>[];
|
||||
final _definitionsWorkList = <Definition>[];
|
||||
|
||||
ConstantPropagation() : super('ConstantPropagation');
|
||||
|
||||
@override
|
||||
void run() {
|
||||
analyze();
|
||||
transform();
|
||||
}
|
||||
|
||||
void analyze() {
|
||||
_addReachableBlock(graph.entryBlock);
|
||||
|
||||
while (_blockWorkList.isNotEmpty || _definitionsWorkList.isNotEmpty) {
|
||||
while (_definitionsWorkList.isNotEmpty) {
|
||||
final def = _definitionsWorkList.removeLast();
|
||||
for (final use in def.inputUses) {
|
||||
final user = use.getInstruction(graph);
|
||||
if (_isReachable(user.block!)) {
|
||||
_visit(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (_blockWorkList.isNotEmpty) {
|
||||
final block = _blockWorkList.removeLast();
|
||||
_visit(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _visit(Instruction instr) {
|
||||
currentInstruction = instr;
|
||||
instr.accept(this);
|
||||
}
|
||||
|
||||
bool _isReachable(Block block) => _reachable[block.preorderNumber];
|
||||
|
||||
void _addReachableBlock(Block block) {
|
||||
if (!_isReachable(block)) {
|
||||
_reachable[block.preorderNumber] = true;
|
||||
_blockWorkList.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isNonConstant(Definition instr) => _nonConstant[instr.id];
|
||||
|
||||
ConstantValue? _getConstantValue(Definition instr) =>
|
||||
instr is Constant ? instr.value : _constantValues[instr];
|
||||
|
||||
void _setConstantValue(Definition instr, ConstantValue result) {
|
||||
assert(!_isNonConstant(instr));
|
||||
assert(instr is! Constant);
|
||||
final old = _constantValues[instr];
|
||||
if (old == null) {
|
||||
// State transition: unknown -> constant.
|
||||
_constantValues[instr] = result;
|
||||
_definitionsWorkList.add(instr);
|
||||
} else {
|
||||
// Constant -> same constant.
|
||||
assert(old == result);
|
||||
}
|
||||
}
|
||||
|
||||
void _setNonConstant(Definition instr) {
|
||||
assert(instr is! Constant);
|
||||
if (!_isNonConstant(instr)) {
|
||||
// State transition: unknown or constant -> non-constant.
|
||||
_nonConstant[instr.id] = true;
|
||||
_constantValues.remove(instr);
|
||||
_definitionsWorkList.add(instr);
|
||||
}
|
||||
}
|
||||
|
||||
void _setResult(Definition instr, ConstantValue? result) {
|
||||
if (result != null) {
|
||||
_setConstantValue(instr, result);
|
||||
} else {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
}
|
||||
|
||||
void _setRedundantPhi(Phi instr, Definition originalInput) {
|
||||
final old = _redundantPhis[instr];
|
||||
if (old == null) {
|
||||
// State transition: unknown -> redundant.
|
||||
_redundantPhis[instr] = originalInput;
|
||||
_definitionsWorkList.add(instr);
|
||||
} else {
|
||||
// No state transition: redundant -> redundant with the same input.
|
||||
assert(old == originalInput);
|
||||
}
|
||||
}
|
||||
|
||||
void _setNonRedundantPhi(Phi instr) {
|
||||
if (_redundantPhis.remove(instr) != null) {
|
||||
// State transition: redundant -> non-redundant.
|
||||
_definitionsWorkList.add(instr);
|
||||
}
|
||||
}
|
||||
|
||||
Definition _unwrapRedundantPhi(Definition def) =>
|
||||
def is Phi ? (_redundantPhis[def] ?? def) : def;
|
||||
|
||||
bool _sameDefinitions(Definition a, Definition b) =>
|
||||
_unwrapRedundantPhi(a) == _unwrapRedundantPhi(b);
|
||||
|
||||
void visitBlock(Block block) {
|
||||
currentBlock = block;
|
||||
assert(_isReachable(block));
|
||||
var canThrow = false;
|
||||
for (final instr in block) {
|
||||
_visit(instr);
|
||||
canThrow = canThrow || instr.canThrow;
|
||||
}
|
||||
final exceptionHandler = block.exceptionHandler;
|
||||
if (exceptionHandler != null && canThrow) {
|
||||
_addReachableBlock(exceptionHandler);
|
||||
}
|
||||
currentBlock = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitEntryBlock(EntryBlock instr) => visitBlock(instr);
|
||||
|
||||
@override
|
||||
void visitJoinBlock(JoinBlock instr) => visitBlock(instr);
|
||||
|
||||
@override
|
||||
void visitTargetBlock(TargetBlock instr) => visitBlock(instr);
|
||||
|
||||
@override
|
||||
void visitCatchBlock(CatchBlock instr) => visitBlock(instr);
|
||||
|
||||
@override
|
||||
void visitGoto(Goto instr) {
|
||||
final target = instr.target;
|
||||
if (!_isReachable(target)) {
|
||||
_addReachableBlock(target);
|
||||
} else {
|
||||
// Re-visit phis in the target block as this predecessor
|
||||
// became reachable.
|
||||
if (target is JoinBlock) {
|
||||
for (final phi in target.phis) {
|
||||
_visit(phi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitBranch(Branch instr) {
|
||||
if (_isNonConstant(instr.condition)) {
|
||||
_addReachableBlock(instr.trueSuccessor);
|
||||
_addReachableBlock(instr.falseSuccessor);
|
||||
return;
|
||||
}
|
||||
ConstantValue? condition = _getConstantValue(instr.condition);
|
||||
if (condition != null) {
|
||||
_addReachableBlock(
|
||||
condition.boolValue ? instr.trueSuccessor : instr.falseSuccessor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitCompareAndBranch(CompareAndBranch instr) {
|
||||
// Comparison and Branch should be optimized separately
|
||||
// before combined to CompareAndBranch.
|
||||
_addReachableBlock(instr.trueSuccessor);
|
||||
_addReachableBlock(instr.falseSuccessor);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTryEntry(TryEntry instr) {
|
||||
_addReachableBlock(instr.tryBody);
|
||||
// Do not mark catch block as reachable here.
|
||||
// Catch block is marked reachable iff any instruction in the try body
|
||||
// can throw.
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPhi(Phi instr) {
|
||||
final preds = instr.block!.predecessors;
|
||||
assert(instr.inputCount == preds.length);
|
||||
var canBeConstant = true;
|
||||
var canBeRedundant = true;
|
||||
ConstantValue? constantValue;
|
||||
Definition? originalInput;
|
||||
for (int i = 0, n = instr.inputCount; i < n; ++i) {
|
||||
if (!_isReachable(preds[i])) {
|
||||
continue;
|
||||
}
|
||||
final input = instr.inputDefAt(i);
|
||||
if (input == instr) {
|
||||
continue;
|
||||
}
|
||||
if (_isNonConstant(input)) {
|
||||
// Any input is non-constant => phi is non-constant.
|
||||
_setNonConstant(instr);
|
||||
canBeConstant = false;
|
||||
} else if (canBeConstant) {
|
||||
ConstantValue? value = _getConstantValue(input);
|
||||
if (value == null) {
|
||||
// Unknown input => unknown phi.
|
||||
canBeConstant = false;
|
||||
} else {
|
||||
if (constantValue == null) {
|
||||
// First constant input is discovered.
|
||||
constantValue = value;
|
||||
} else if (constantValue != value) {
|
||||
// Two constant inputs with different values =>
|
||||
// phi is non-constant.
|
||||
_setNonConstant(instr);
|
||||
canBeConstant = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canBeRedundant) {
|
||||
if (originalInput == null) {
|
||||
originalInput = input;
|
||||
} else if (input != originalInput) {
|
||||
canBeRedundant = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canBeConstant) {
|
||||
_setConstantValue(instr, constantValue!);
|
||||
}
|
||||
if (canBeRedundant) {
|
||||
_setRedundantPhi(instr, originalInput!);
|
||||
} else {
|
||||
_setNonRedundantPhi(instr);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitReturn(Return instr) {}
|
||||
|
||||
@override
|
||||
void visitConstant(Constant instr) {
|
||||
// There is no need to flood _constantValues map with Constant
|
||||
// instructions as they are handled in _getConstantValue directly
|
||||
// and will not be replaced.
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDirectCall(DirectCall instr) {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitInterfaceCall(InterfaceCall instr) {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDynamicCall(DynamicCall instr) {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitParameter(Parameter instr) {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitLoadLocal(LoadLocal instr) =>
|
||||
throw 'Should not be used in SSA form.';
|
||||
|
||||
@override
|
||||
void visitStoreLocal(StoreLocal instr) =>
|
||||
throw 'Should not be used in SSA form.';
|
||||
|
||||
@override
|
||||
void visitThrow(Throw instr) {}
|
||||
|
||||
@override
|
||||
void visitTypeParameters(TypeParameters instr) {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTypeCast(TypeCast instr) {
|
||||
if (_isNonConstant(instr.operand)) {
|
||||
_setNonConstant(instr);
|
||||
return;
|
||||
}
|
||||
ConstantValue? operand = _getConstantValue(instr.operand);
|
||||
if (operand != null) {
|
||||
if (operand.type.isSubtypeOf(instr.testedType)) {
|
||||
_setResult(instr, operand);
|
||||
} else {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTypeTest(TypeTest instr) {
|
||||
if (_isNonConstant(instr.operand)) {
|
||||
_setNonConstant(instr);
|
||||
return;
|
||||
}
|
||||
ConstantValue? operand = _getConstantValue(instr.operand);
|
||||
if (operand != null) {
|
||||
_setResult(
|
||||
instr,
|
||||
ConstantValue.fromBool(operand.type.isSubtypeOf(instr.testedType)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTypeArguments(TypeArguments instr) {
|
||||
_setNonConstant(instr);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitComparison(Comparison instr) {
|
||||
switch (instr.op) {
|
||||
case ComparisonOpcode.equal:
|
||||
case ComparisonOpcode.identical:
|
||||
case ComparisonOpcode.intEqual:
|
||||
if (_sameDefinitions(instr.left, instr.right)) {
|
||||
_setResult(instr, ConstantValue.fromBool(true));
|
||||
return;
|
||||
}
|
||||
case ComparisonOpcode.notEqual:
|
||||
case ComparisonOpcode.notIdentical:
|
||||
case ComparisonOpcode.intNotEqual:
|
||||
if (_sameDefinitions(instr.left, instr.right)) {
|
||||
_setResult(instr, ConstantValue.fromBool(false));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
}
|
||||
if (_isNonConstant(instr.left) || _isNonConstant(instr.right)) {
|
||||
_setNonConstant(instr);
|
||||
return;
|
||||
}
|
||||
ConstantValue? left = _getConstantValue(instr.left);
|
||||
ConstantValue? right = _getConstantValue(instr.right);
|
||||
if (left != null && right != null) {
|
||||
ConstantValue result = constantFolding.comparison(instr.op, left, right);
|
||||
_setResult(instr, result);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitBinaryIntOp(BinaryIntOp instr) {
|
||||
if (_isNonConstant(instr.left) || _isNonConstant(instr.right)) {
|
||||
_setNonConstant(instr);
|
||||
return;
|
||||
}
|
||||
ConstantValue? left = _getConstantValue(instr.left);
|
||||
ConstantValue? right = _getConstantValue(instr.right);
|
||||
if (left != null && right != null) {
|
||||
ConstantValue? result = constantFolding.binaryIntOp(
|
||||
instr.op,
|
||||
left,
|
||||
right,
|
||||
);
|
||||
_setResult(instr, result);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitUnaryIntOp(UnaryIntOp instr) {
|
||||
if (_isNonConstant(instr.operand)) {
|
||||
_setNonConstant(instr);
|
||||
return;
|
||||
}
|
||||
ConstantValue? operand = _getConstantValue(instr.operand);
|
||||
if (operand != null) {
|
||||
ConstantValue? result = constantFolding.unaryIntOp(instr.op, operand);
|
||||
_setResult(instr, result);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitBinaryDoubleOp(BinaryDoubleOp instr) {
|
||||
if (_isNonConstant(instr.left) || _isNonConstant(instr.right)) {
|
||||
_setNonConstant(instr);
|
||||
return;
|
||||
}
|
||||
ConstantValue? left = _getConstantValue(instr.left);
|
||||
ConstantValue? right = _getConstantValue(instr.right);
|
||||
if (left != null && right != null) {
|
||||
ConstantValue? result = constantFolding.binaryDoubleOp(
|
||||
instr.op,
|
||||
left,
|
||||
right,
|
||||
);
|
||||
_setResult(instr, result);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitUnaryDoubleOp(UnaryDoubleOp instr) {
|
||||
if (_isNonConstant(instr.operand)) {
|
||||
_setNonConstant(instr);
|
||||
return;
|
||||
}
|
||||
ConstantValue? operand = _getConstantValue(instr.operand);
|
||||
if (operand != null) {
|
||||
ConstantValue? result = constantFolding.unaryDoubleOp(instr.op, operand);
|
||||
_setResult(instr, result);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitParallelMove(ParallelMove instr) {}
|
||||
|
||||
void transform() {
|
||||
for (final entry in _constantValues.entries) {
|
||||
final instr = entry.key;
|
||||
final constantValue = entry.value;
|
||||
instr.replaceUsesWith(graph.getConstant(constantValue));
|
||||
instr.removeFromGraph();
|
||||
}
|
||||
for (final entry in _redundantPhis.entries) {
|
||||
final instr = entry.key;
|
||||
final input = entry.value;
|
||||
if (!_constantValues.containsKey(instr)) {
|
||||
assert(!_constantValues.containsKey(input));
|
||||
instr.replaceUsesWith(input);
|
||||
instr.removeFromGraph();
|
||||
}
|
||||
}
|
||||
var recomputeControlFlow = false;
|
||||
for (final block in graph.preorder) {
|
||||
if (!_isReachable(block)) {
|
||||
for (final instr in block) {
|
||||
instr.removeInputsFromUseLists();
|
||||
}
|
||||
recomputeControlFlow = true;
|
||||
}
|
||||
}
|
||||
if (!recomputeControlFlow) {
|
||||
graph.invalidateInstructionNumbering();
|
||||
return;
|
||||
}
|
||||
for (final block in graph.preorder) {
|
||||
if (_isReachable(block)) {
|
||||
if (block is JoinBlock) {
|
||||
_transformPhis(block);
|
||||
}
|
||||
block.exceptionHandler = _transformExceptionHandler(
|
||||
block.exceptionHandler,
|
||||
);
|
||||
_transformLastInstruction(block);
|
||||
}
|
||||
}
|
||||
graph.discoverBlocks();
|
||||
}
|
||||
|
||||
void _transformPhis(JoinBlock block) {
|
||||
var inputCount = 0;
|
||||
for (int i = 0, n = block.predecessors.length; i < n; ++i) {
|
||||
if (_isReachable(block.predecessors[i])) {
|
||||
if (inputCount < i) {
|
||||
// Move inputs corresponding to the reachable predecessor.
|
||||
for (final phi in block.phis) {
|
||||
phi.removeInputFromUseList(i);
|
||||
phi.setInputAt(inputCount, phi.inputDefAt(i));
|
||||
phi.addInputToUseList(inputCount);
|
||||
}
|
||||
}
|
||||
++inputCount;
|
||||
} else {
|
||||
// Remove inputs corresponding to the unreachable predecessor.
|
||||
for (final phi in block.phis) {
|
||||
phi.removeInputFromUseList(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inputCount < block.predecessors.length) {
|
||||
// Adjust number of inputs.
|
||||
for (final phi in block.phis) {
|
||||
phi.truncateInputs(inputCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CatchBlock? _transformExceptionHandler(CatchBlock? handler) {
|
||||
while (handler != null && !_isReachable(handler)) {
|
||||
handler = handler.exceptionHandler;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
TargetBlock? _getSingleSuccessor(Block block) {
|
||||
final last = block.lastInstruction;
|
||||
switch (last) {
|
||||
case Goto():
|
||||
assert(_isReachable(last.target));
|
||||
return null;
|
||||
case Branch(:var condition):
|
||||
if (condition is Constant) {
|
||||
return condition.value.boolValue
|
||||
? last.trueSuccessor
|
||||
: last.falseSuccessor;
|
||||
} else {
|
||||
assert(_isReachable(last.trueSuccessor));
|
||||
assert(_isReachable(last.falseSuccessor));
|
||||
return null;
|
||||
}
|
||||
case TryEntry():
|
||||
assert(_isReachable(last.tryBody));
|
||||
if (!_isReachable(last.catchBlock)) {
|
||||
return last.tryBody;
|
||||
}
|
||||
return null;
|
||||
case Return() || Throw():
|
||||
return null;
|
||||
default:
|
||||
throw 'Unexpected block end ${last.runtimeType}';
|
||||
}
|
||||
}
|
||||
|
||||
void _transformLastInstruction(Block block) {
|
||||
TargetBlock? successor = _getSingleSuccessor(block);
|
||||
if (successor == null) {
|
||||
return;
|
||||
}
|
||||
assert(_isReachable(successor));
|
||||
// Replace last instruction with Goto.
|
||||
final last = block.lastInstruction;
|
||||
last.removeFromGraph();
|
||||
block.lastInstruction.appendInstruction(Goto(graph, last.sourcePosition));
|
||||
block.successors.clear();
|
||||
block.successors.add(successor);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import 'package:cfg/front_end/recognized_methods.dart';
|
||||
import 'package:cfg/ir/functions.dart';
|
||||
import 'package:cfg/ir/ir_to_text.dart';
|
||||
import 'package:cfg/ir/ssa_computation.dart';
|
||||
import 'package:cfg/passes/constant_propagation.dart';
|
||||
import 'package:cfg/passes/pass.dart';
|
||||
import 'package:cfg/passes/simplification.dart';
|
||||
import 'package:cfg/passes/value_numbering.dart';
|
||||
@@ -115,6 +116,7 @@ class CompileAndDumpIr extends RecursiveVisitor {
|
||||
final pipeline = Pipeline([
|
||||
SSAComputation(),
|
||||
ValueNumbering(simplification: Simplification()),
|
||||
ConstantPropagation(),
|
||||
]);
|
||||
pipeline.run(graph);
|
||||
buffer.writeln('--- ${node.name}');
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2025, 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.
|
||||
|
||||
int test1(int x) {
|
||||
int y;
|
||||
if (x == x) {
|
||||
if (x + 1 == x + 1) {
|
||||
y = 1;
|
||||
} else {
|
||||
y = 3;
|
||||
}
|
||||
} else {
|
||||
y = 5;
|
||||
}
|
||||
return y + 1;
|
||||
}
|
||||
|
||||
void test2() {
|
||||
var x = 0;
|
||||
for (;;) {
|
||||
if (x == 0) break;
|
||||
x = x + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void test3(int x, int y) {
|
||||
if (true) {
|
||||
x = 10;
|
||||
}
|
||||
var z = y;
|
||||
if (x > 20) {
|
||||
z = y + 10;
|
||||
}
|
||||
try {
|
||||
++z;
|
||||
if (x > 30) {
|
||||
print('I can throw');
|
||||
}
|
||||
} catch (_) {
|
||||
print(z);
|
||||
}
|
||||
}
|
||||
|
||||
void test4(int x, int z) {
|
||||
if (true) {
|
||||
x = 10;
|
||||
}
|
||||
int y;
|
||||
switch (x) {
|
||||
case 1:
|
||||
y = 10;
|
||||
case 2:
|
||||
y = 20;
|
||||
case 10:
|
||||
y = z;
|
||||
default:
|
||||
y = -1;
|
||||
}
|
||||
print(y);
|
||||
}
|
||||
|
||||
void main() {}
|
||||
@@ -0,0 +1,98 @@
|
||||
--- test1
|
||||
B0 = EntryBlock() dominates:(B5)
|
||||
v9 = Constant(1)
|
||||
Constant(3)
|
||||
Constant(5)
|
||||
Constant(true)
|
||||
v34 = Constant(2)
|
||||
v1 = Parameter(x)
|
||||
Goto(B5)
|
||||
B5 = TargetBlock() idom:B0 dominates:(B14)
|
||||
BinaryIntOp +(v1, v9)
|
||||
Goto(B14)
|
||||
B14 = TargetBlock() idom:B5 dominates:(B18)
|
||||
Goto(B18)
|
||||
B18 = JoinBlock(B14) idom:B14 dominates:(B23)
|
||||
Goto(B23)
|
||||
B23 = JoinBlock(B18) idom:B18
|
||||
Return(v34)
|
||||
|
||||
--- test2
|
||||
B0 = EntryBlock() dominates:(B3)
|
||||
Constant(0)
|
||||
Constant(1)
|
||||
v19 = Constant(null)
|
||||
Constant(true)
|
||||
Goto(B3)
|
||||
B3 = JoinBlock(B0) idom:B0 dominates:(B7)
|
||||
Goto(B7)
|
||||
B7 = TargetBlock() idom:B3 dominates:(B12)
|
||||
Goto(B12)
|
||||
B12 = JoinBlock(B7) idom:B7
|
||||
Return(v19)
|
||||
|
||||
--- test3
|
||||
B0 = EntryBlock() dominates:(B4)
|
||||
Constant(true)
|
||||
Constant(10)
|
||||
Constant(20)
|
||||
v30 = Constant(1)
|
||||
Constant(30)
|
||||
Constant("I can throw")
|
||||
v53 = Constant(null)
|
||||
Constant(false)
|
||||
Parameter(x)
|
||||
v2 = Parameter(y)
|
||||
Goto(B4)
|
||||
B4 = TargetBlock() idom:B0 dominates:(B7)
|
||||
Goto(B7)
|
||||
B7 = JoinBlock(B4) idom:B4 dominates:(B18)
|
||||
Goto(B18)
|
||||
B18 = TargetBlock() idom:B7 dominates:(B20)
|
||||
Goto(B20)
|
||||
B20 = JoinBlock(B18) idom:B18 dominates:(B26)
|
||||
Goto(B26)
|
||||
B26 = TargetBlock() idom:B20 dominates:(B37)
|
||||
BinaryIntOp +(v2, v30)
|
||||
Goto(B37)
|
||||
B37 = TargetBlock() idom:B26 dominates:(B39)
|
||||
Goto(B39)
|
||||
B39 = JoinBlock(B37) idom:B37 dominates:(B44)
|
||||
Goto(B44)
|
||||
B44 = JoinBlock(B39) idom:B39
|
||||
Return(v53)
|
||||
|
||||
--- test4
|
||||
B0 = EntryBlock() dominates:(B4)
|
||||
Constant(true)
|
||||
Constant(10)
|
||||
Constant(1)
|
||||
Constant(2)
|
||||
Constant(20)
|
||||
v49 = Constant(null)
|
||||
Constant(-1)
|
||||
Constant(false)
|
||||
Parameter(x)
|
||||
v2 = Parameter(z)
|
||||
Goto(B4)
|
||||
B4 = TargetBlock() idom:B0 dominates:(B7)
|
||||
Goto(B7)
|
||||
B7 = JoinBlock(B4) idom:B4 dominates:(B20)
|
||||
Goto(B20)
|
||||
B20 = TargetBlock() idom:B7 dominates:(B26)
|
||||
Goto(B26)
|
||||
B26 = TargetBlock() idom:B20 dominates:(B30)
|
||||
Goto(B30)
|
||||
B30 = TargetBlock() idom:B26 dominates:(B15)
|
||||
Goto(B15)
|
||||
B15 = JoinBlock(B30) idom:B30 dominates:(B36)
|
||||
Goto(B36)
|
||||
B36 = JoinBlock(B15) idom:B15
|
||||
DirectCall print(v2)
|
||||
Return(v49)
|
||||
|
||||
--- main
|
||||
B0 = EntryBlock()
|
||||
v1 = Constant(null)
|
||||
Return(v1)
|
||||
|
||||
@@ -84,12 +84,12 @@ B0 = EntryBlock() dominates:(B8, B34, B4, B3, B7)
|
||||
v27 = Constant(10)
|
||||
v38 = Constant(null)
|
||||
v1 = Parameter(i)
|
||||
v6 = InterfaceCall Object.==(v5, v1)
|
||||
v6 = Comparison int ==(v1, v5)
|
||||
Branch(v6, true: B7, false: B8)
|
||||
B7 = TargetBlock() idom:B0
|
||||
Goto(B3)
|
||||
B8 = TargetBlock() idom:B0 dominates:(B14, B13)
|
||||
v12 = InterfaceCall Object.==(v11, v1)
|
||||
v12 = Comparison int ==(v1, v11)
|
||||
Branch(v12, true: B13, false: B14)
|
||||
B13 = TargetBlock() idom:B8
|
||||
Goto(B4)
|
||||
|
||||
@@ -224,12 +224,12 @@ B0 = EntryBlock() dominates:(B9, B34, B3, B8)
|
||||
v39 = Constant("3-4")
|
||||
v43 = Constant(null)
|
||||
v1 = Parameter(x)
|
||||
v7 = InterfaceCall Object.==(v6, v1)
|
||||
v7 = Comparison int ==(v1, v6)
|
||||
Branch(v7, true: B8, false: B9)
|
||||
B8 = TargetBlock() idom:B0
|
||||
Goto(B3)
|
||||
B9 = TargetBlock() idom:B0 dominates:(B15, B14)
|
||||
v13 = InterfaceCall Object.==(v12, v1)
|
||||
v13 = Comparison int ==(v1, v12)
|
||||
Branch(v13, true: B14, false: B15)
|
||||
B14 = TargetBlock() idom:B9
|
||||
Goto(B3)
|
||||
@@ -237,7 +237,7 @@ B3 = JoinBlock(B14, B8) idom:B0
|
||||
DirectCall print(v32)
|
||||
Goto(B34)
|
||||
B15 = TargetBlock() idom:B9 dominates:(B21, B30, B5, B20)
|
||||
v19 = InterfaceCall Object.==(v18, v1)
|
||||
v19 = Comparison int ==(v1, v18)
|
||||
Branch(v19, true: B20, false: B21)
|
||||
B20 = TargetBlock() idom:B15 dominates:(B4)
|
||||
Goto(B4)
|
||||
@@ -245,7 +245,7 @@ B4 = JoinBlock(B20) idom:B20
|
||||
DirectCall print(v36)
|
||||
Goto(B5)
|
||||
B21 = TargetBlock() idom:B15 dominates:(B27, B26)
|
||||
v25 = InterfaceCall Object.==(v24, v1)
|
||||
v25 = Comparison int ==(v1, v24)
|
||||
Branch(v25, true: B26, false: B27)
|
||||
B26 = TargetBlock() idom:B21
|
||||
Goto(B5)
|
||||
@@ -266,19 +266,13 @@ B0 = EntryBlock() dominates:(B4, B24, B3)
|
||||
v11 = Constant(3)
|
||||
v35 = Constant(null)
|
||||
TryEntry(try-body: B3, catch-block: B4)
|
||||
B3 = TargetBlock() exception-handler:B4 idom:B0 dominates:(B9, B8)
|
||||
TryEntry(try-body: B8, catch-block: B9)
|
||||
B8 = TargetBlock() exception-handler:B9 idom:B3 dominates:(B13)
|
||||
B3 = TargetBlock() exception-handler:B4 idom:B0 dominates:(B8)
|
||||
Goto(B8)
|
||||
B8 = TargetBlock() exception-handler:B4 idom:B3 dominates:(B13)
|
||||
Goto(B13)
|
||||
B13 = JoinBlock(B8) exception-handler:B4 idom:B8
|
||||
DirectCall print(v11)
|
||||
Goto(B24)
|
||||
B9 = CatchBlock() exception-handler:B4 idom:B3
|
||||
v37 = Parameter(x)
|
||||
v15 = Parameter(#exception)
|
||||
v16 = Parameter(#stackTrace)
|
||||
DirectCall print(v37)
|
||||
Throw(v15, v16)
|
||||
B4 = CatchBlock() idom:B0
|
||||
v38 = Parameter(x)
|
||||
v26 = Parameter(#exception)
|
||||
@@ -293,75 +287,38 @@ B24 = JoinBlock(B4, B13) idom:B0
|
||||
B0 = EntryBlock() dominates:(B2, B1)
|
||||
v4 = Constant(10)
|
||||
v6 = Constant(0)
|
||||
v11 = Constant(5)
|
||||
v22 = Constant(3)
|
||||
v34 = Constant(1)
|
||||
v45 = Constant(2)
|
||||
Constant(5)
|
||||
Constant(3)
|
||||
Constant(1)
|
||||
Constant(2)
|
||||
v59 = Constant(20)
|
||||
v84 = Constant(42)
|
||||
Constant(true)
|
||||
TryEntry(try-body: B1, catch-block: B2)
|
||||
B1 = TargetBlock() exception-handler:B2 idom:B0 dominates:(B8)
|
||||
DirectCall print(v4)
|
||||
Goto(B8)
|
||||
B8 = JoinBlock(B1, B68) exception-handler:B2 idom:B1 dominates:(B14, B65, B13) loop-header (depth:1 body:(B8, B13, B16, B26, B28, B37, B39, B48, B50, B55, B68, B36, B41) back-edges:(B68))
|
||||
v88 = Phi(v6, v76)
|
||||
v12 = Comparison int <(v88, v11)
|
||||
Branch(v12, true: B13, false: B14)
|
||||
B13 = TargetBlock() exception-handler:B2 idom:B8 dominates:(B17, B16) in-loop:B8
|
||||
B8 = JoinBlock(B1) exception-handler:B2 idom:B1 dominates:(B13)
|
||||
Goto(B13)
|
||||
B13 = TargetBlock() exception-handler:B2 idom:B8 dominates:(B17, B16)
|
||||
TryEntry(try-body: B16, catch-block: B17)
|
||||
B16 = TargetBlock() exception-handler:B17 idom:B13 dominates:(B26, B25) in-loop:B8
|
||||
DirectCall print(v88)
|
||||
v23 = BinaryIntOp %(v88, v22)
|
||||
v24 = Comparison int ==(v23, v6)
|
||||
Branch(v24, true: B25, false: B26)
|
||||
B16 = TargetBlock() exception-handler:B17 idom:B13 dominates:(B25)
|
||||
DirectCall print(v6)
|
||||
Goto(B25)
|
||||
B25 = TargetBlock() exception-handler:B17 idom:B16 dominates:(B30)
|
||||
Goto(B30)
|
||||
B30 = JoinBlock(B25) exception-handler:B2 idom:B25
|
||||
B30 = JoinBlock(B25) exception-handler:B2 idom:B25 dominates:(B65)
|
||||
DirectCall print(v59)
|
||||
Goto(B65)
|
||||
B26 = TargetBlock() exception-handler:B17 idom:B16 dominates:(B28) in-loop:B8
|
||||
Goto(B28)
|
||||
B28 = JoinBlock(B26) exception-handler:B17 idom:B26 dominates:(B37, B68, B36) in-loop:B8
|
||||
v35 = Comparison int ==(v23, v34)
|
||||
Branch(v35, true: B36, false: B37)
|
||||
B36 = TargetBlock() exception-handler:B17 idom:B28 dominates:(B41) in-loop:B8
|
||||
Goto(B41)
|
||||
B41 = JoinBlock(B36) exception-handler:B2 idom:B36 in-loop:B8
|
||||
DirectCall print(v59)
|
||||
Goto(B68)
|
||||
B37 = TargetBlock() exception-handler:B17 idom:B28 dominates:(B39) in-loop:B8
|
||||
Goto(B39)
|
||||
B39 = JoinBlock(B37) exception-handler:B17 idom:B37 dominates:(B48, B47) in-loop:B8
|
||||
v46 = Comparison int ==(v23, v45)
|
||||
Branch(v46, true: B47, false: B48)
|
||||
B47 = TargetBlock() exception-handler:B17 idom:B39 dominates:(B53)
|
||||
Goto(B53)
|
||||
B53 = JoinBlock(B47) exception-handler:B2 idom:B47 dominates:(B71)
|
||||
DirectCall print(v59)
|
||||
Goto(B71)
|
||||
B71 = JoinBlock(B53) idom:B53
|
||||
B65 = JoinBlock(B30) exception-handler:B2 idom:B30 dominates:(B80)
|
||||
Goto(B80)
|
||||
B80 = JoinBlock(B65) idom:B65
|
||||
Return(v84)
|
||||
B48 = TargetBlock() exception-handler:B17 idom:B39 dominates:(B50) in-loop:B8
|
||||
Goto(B50)
|
||||
B50 = JoinBlock(B48) exception-handler:B17 idom:B48 dominates:(B55) in-loop:B8
|
||||
Goto(B55)
|
||||
B55 = JoinBlock(B50) exception-handler:B2 idom:B50 in-loop:B8
|
||||
DirectCall print(v59)
|
||||
Goto(B68)
|
||||
B68 = JoinBlock(B55, B41) exception-handler:B2 idom:B28 in-loop:B8
|
||||
v76 = BinaryIntOp +(v88, v34)
|
||||
Goto(B8)
|
||||
B17 = CatchBlock() exception-handler:B2 idom:B13
|
||||
v57 = Parameter(#exception)
|
||||
v58 = Parameter(#stackTrace)
|
||||
DirectCall print(v59)
|
||||
Throw(v57, v58)
|
||||
B14 = TargetBlock() exception-handler:B2 idom:B8
|
||||
Goto(B65)
|
||||
B65 = JoinBlock(B14, B30) exception-handler:B2 idom:B8 dominates:(B80)
|
||||
Goto(B80)
|
||||
B80 = JoinBlock(B65) idom:B65
|
||||
Return(v84)
|
||||
B2 = CatchBlock() idom:B0
|
||||
Parameter(#exception)
|
||||
Parameter(#stackTrace)
|
||||
|
||||
@@ -64,18 +64,16 @@ B10 = TargetBlock() idom:B4
|
||||
B0 = EntryBlock() dominates:(B4)
|
||||
v2 = Constant(0)
|
||||
Constant(2)
|
||||
v20 = Constant("Bye")
|
||||
Constant("Bye")
|
||||
v22 = Constant(null)
|
||||
v25 = Constant(false)
|
||||
Constant(false)
|
||||
v1 = Parameter(n)
|
||||
Goto(B4)
|
||||
B4 = JoinBlock(B0) idom:B0 dominates:(B10, B9)
|
||||
v8 = Comparison int >=(v1, v2)
|
||||
Branch(v8, true: B9, false: B10)
|
||||
B9 = TargetBlock() idom:B4 dominates:(B16, B15)
|
||||
Branch(v25, true: B15, false: B16)
|
||||
B15 = TargetBlock() idom:B9
|
||||
Throw(v20)
|
||||
B9 = TargetBlock() idom:B4 dominates:(B16)
|
||||
Goto(B16)
|
||||
B16 = TargetBlock() idom:B9 dominates:(B18)
|
||||
Goto(B18)
|
||||
B18 = JoinBlock(B16) idom:B16
|
||||
@@ -91,18 +89,13 @@ B0 = EntryBlock() dominates:(B2, B1)
|
||||
v20 = Constant(3)
|
||||
v22 = Constant("Bye-bye")
|
||||
TryEntry(try-body: B1, catch-block: B2)
|
||||
B1 = TargetBlock() exception-handler:B2 idom:B0 dominates:(B5, B4)
|
||||
TryEntry(try-body: B4, catch-block: B5)
|
||||
B4 = TargetBlock() exception-handler:B5 idom:B1 dominates:(B7)
|
||||
B1 = TargetBlock() exception-handler:B2 idom:B0 dominates:(B4)
|
||||
Goto(B4)
|
||||
B4 = TargetBlock() exception-handler:B2 idom:B1 dominates:(B7)
|
||||
Goto(B7)
|
||||
B7 = JoinBlock(B4) exception-handler:B2 idom:B4
|
||||
DirectCall print(v11)
|
||||
Throw(v13)
|
||||
B5 = CatchBlock() exception-handler:B2 idom:B1
|
||||
Parameter(#exception)
|
||||
Parameter(#stackTrace)
|
||||
DirectCall print(v11)
|
||||
Throw(v13)
|
||||
B2 = CatchBlock() idom:B0
|
||||
Parameter(#exception)
|
||||
Parameter(#stackTrace)
|
||||
|
||||
Reference in New Issue
Block a user