blob: f7c19c7734e7e0fd83a316a8278f7adf2e538022 [file] [log] [blame] [edit]
// Copyright (c) 2026, 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/functions.dart';
import 'package:cfg/ir/global_context.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/ir/visitor.dart';
import 'package:cfg/passes/pass.dart';
import 'package:cfg/utils/misc.dart';
import 'package:kernel/ast.dart' as ast;
/// IR lowering for native back-end.
///
/// Can replace instructions with multiple low-level
/// instructions or combine instructions and their inputs
/// into a single low-level instruction.
///
/// TODO: insert boxing/unboxing
final class Lowering extends Pass with DefaultInstructionVisitor<void> {
final FunctionRegistry functionRegistry;
Lowering(this.functionRegistry) : super('Lowering');
late final CFunction _growableListLiteral = functionRegistry.getFunction(
GlobalContext.instance.coreTypes.index.getProcedure(
'dart:core',
'_GrowableList',
'_literal',
),
);
late final CFunction _mapFromLiteral = functionRegistry.getFunction(
GlobalContext.instance.coreTypes.index.getProcedure(
'dart:core',
'Map',
'_fromLiteral',
),
);
late final CFunction _interpolateSingle = functionRegistry.getFunction(
GlobalContext.instance.coreTypes.index.getProcedure(
'dart:core',
'_StringBase',
'_interpolateSingle',
),
);
late final CFunction _interpolate = functionRegistry.getFunction(
GlobalContext.instance.coreTypes.index.getProcedure(
'dart:core',
'_StringBase',
'_interpolate',
),
);
late final _emptyList = ConstantValue(
ast.InstanceConstant(
GlobalContext.instance.coreTypes.listClass.reference,
const <ast.DartType>[const ast.DynamicType()],
const {},
),
);
@override
void run() {
for (final block in graph.reversePostorder) {
currentBlock = block;
for (final instr in block) {
currentInstruction = instr;
instr.accept(this);
}
}
graph.invalidateInstructionNumbering();
}
@override
void defaultInstruction(Instruction instr) {}
@override
void visitComparison(Comparison instr) {
// Combine (a & b) == 0, (a & b) != 0, (a & bit) == bit, (a & bit) != bit
// into an intTestIsZero/intTestIsNotZero comparison.
final op = instr.op;
if (op == ComparisonOpcode.intEqual || op == ComparisonOpcode.intNotEqual) {
final left = instr.left;
if (left is BinaryIntOp &&
left.op == BinaryIntOpcode.bitAnd &&
left.singleUser == instr) {
final right = instr.right;
if (right is Constant) {
final value = right.value.intValue;
if (value == 0 || (isPowerOf2(value) && left.right == right)) {
instr.op = ((op == ComparisonOpcode.intEqual) == (value == 0))
? ComparisonOpcode.intTestIsZero
: ComparisonOpcode.intTestIsNotZero;
instr.replaceInputAt(0, left.left);
instr.replaceInputAt(1, left.right);
left.removeFromGraph();
}
}
}
}
}
@override
void visitBranch(Branch instr) {
// Combine comparisons and branches.
final condition = instr.condition;
if (condition is Comparison && condition.singleUser == instr) {
final op = condition.op;
switch (condition.op) {
// TODO(alexmarkov): support double comparisons
case ComparisonOpcode.equal:
case ComparisonOpcode.notEqual:
case ComparisonOpcode.intEqual:
case ComparisonOpcode.intNotEqual:
case ComparisonOpcode.intLess:
case ComparisonOpcode.intLessOrEqual:
case ComparisonOpcode.intGreater:
case ComparisonOpcode.intGreaterOrEqual:
case ComparisonOpcode.intTestIsZero:
case ComparisonOpcode.intTestIsNotZero:
final left = condition.left;
final right = condition.right;
condition.removeFromGraph();
final block = instr.block!;
final sourcePosition = instr.sourcePosition;
instr.removeFromGraph();
block.lastInstruction.appendInstruction(
CompareAndBranch(graph, sourcePosition, op, left, right),
);
break;
default:
break;
}
}
}
@override
void visitAllocateListLiteral(AllocateListLiteral instr) {
// List literals up to 8 elements are lowered in the front-end
// (pkg/vm/lib/transformations/list_literals_lowering.dart)
assert(instr.length > 8);
final argument = AllocateList(
graph,
instr.sourcePosition,
graph.getConstant(ConstantValue.fromInt(instr.length)),
);
argument.insertBefore(instr);
for (int i = 0, n = instr.length; i < n; ++i) {
final setElem = SetListElement(
graph,
instr.sourcePosition,
argument,
graph.getConstant(ConstantValue.fromInt(i)),
instr.elementAt(i),
);
setElem.insertBefore(instr);
}
final replacement = DirectCall(
graph,
instr.sourcePosition,
_growableListLiteral,
instr.type,
inputCount: 2,
);
replacement.setInputAt(0, instr.typeArguments);
replacement.setInputAt(1, argument);
replacement.insertBefore(instr);
instr.replaceUsesWith(replacement);
instr.removeFromGraph();
}
@override
void visitAllocateMapLiteral(AllocateMapLiteral instr) {
Definition argument;
if (instr.length == 0) {
argument = graph.getConstant(_emptyList);
} else {
argument = AllocateList(
graph,
instr.sourcePosition,
graph.getConstant(ConstantValue.fromInt(instr.length << 1)),
);
argument.insertBefore(instr);
for (int i = 0, n = instr.length; i < n; ++i) {
final setKey = SetListElement(
graph,
instr.sourcePosition,
argument,
graph.getConstant(ConstantValue.fromInt((i << 1) + 0)),
instr.keyAt(i),
);
setKey.insertBefore(instr);
final setValue = SetListElement(
graph,
instr.sourcePosition,
argument,
graph.getConstant(ConstantValue.fromInt((i << 1) + 1)),
instr.valueAt(i),
);
setValue.insertBefore(instr);
}
}
final replacement = DirectCall(
graph,
instr.sourcePosition,
_mapFromLiteral,
instr.type,
inputCount: 2,
);
replacement.setInputAt(0, instr.typeArguments);
replacement.setInputAt(1, argument);
replacement.insertBefore(instr);
instr.replaceUsesWith(replacement);
instr.removeFromGraph();
}
@override
void visitStringInterpolation(StringInterpolation instr) {
assert(instr.inputCount > 0);
final isSingle = (instr.inputCount == 1);
final CFunction target = isSingle ? _interpolateSingle : _interpolate;
Definition argument;
if (isSingle) {
argument = instr.inputDefAt(0);
} else {
argument = AllocateList(
graph,
instr.sourcePosition,
graph.getConstant(ConstantValue.fromInt(instr.inputCount)),
);
argument.insertBefore(instr);
for (int i = 0, n = instr.inputCount; i < n; ++i) {
final setElem = SetListElement(
graph,
instr.sourcePosition,
argument,
graph.getConstant(ConstantValue.fromInt(i)),
instr.inputDefAt(i),
);
setElem.insertBefore(instr);
}
}
final replacement = DirectCall(
graph,
instr.sourcePosition,
target,
const StringType(),
inputCount: 1,
);
replacement.setInputAt(0, argument);
replacement.insertBefore(instr);
instr.replaceUsesWith(replacement);
instr.removeFromGraph();
}
}