blob: 13f3a9fe4baf09bfdbdb19674fffd026ebd6a0da [file] [log] [blame]
// 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_system.dart';
import 'package:analyzer/src/wolf/ir/call_descriptor.dart';
import 'package:analyzer/src/wolf/ir/coded_ir.dart';
import 'package:analyzer/src/wolf/ir/ir.dart';
/// Converts [functionBody] to a sequence of IR instructions.
///
/// Caller should pass in the [executableElement] for the method or function to
/// be converted (this is used as a source of type information).
///
/// During conversion, progress information will be reported to [eventListener]
/// (if provided). This should provide enough information to allow the caller to
/// map individual instructions to the AST nodes that spawned them.
CodedIRContainer astToIR(
ExecutableElement executableElement, FunctionBody functionBody,
{required TypeProvider typeProvider,
required TypeSystem typeSystem,
AstToIREventListener? eventListener}) {
eventListener ??= AstToIREventListener();
var visitor = _AstToIRVisitor(
typeSystem: typeSystem,
typeProvider: typeProvider,
eventListener: eventListener);
eventListener._visitor = visitor;
visitor.visitFunctionBody(executableElement, functionBody,
isInstanceMember: !executableElement.isStatic);
var result = visitor.finish();
eventListener._visitor = null;
return result;
}
/// Event listener used by [astToIR] to report progress information.
///
/// By itself this class does nothing; the caller of [astToIR] should make a
/// derived class that overrides one or more of the `on...` methods.
base class AstToIREventListener {
late _AstToIRVisitor? _visitor;
/// The address of the next instruction that [astToIR] will generate.
int get nextInstructionAddress => _visitor!.ir.nextInstructionAddress;
/// Called when [astToIR] is about to visit an AST node.
void onEnterNode(AstNode node) {}
/// Called after [astToIR] has finished vising an AST node.
void onExitNode() {}
/// Called when [astToIR] has completely finished IR generation.
void onFinished(CodedIRContainer ir) {}
}
/// Visitor that converts AST nodes to IR instructions.
///
/// Visit methods that handle L-values (expressions that may appear on the left
/// hand side of an assignment) visit subexpressions of the L-value and then
/// return an instance of [_LValueTemplates] so that the caller can decide what
/// to do next.
///
/// The remaining visit methods simply return `null`.
class _AstToIRVisitor extends ThrowingAstVisitor<_LValueTemplates> {
final TypeSystem typeSystem;
final TypeProvider typeProvider;
final LibraryElement coreLibrary;
final AstToIREventListener eventListener;
/// For each enclosing flow control construct that may be the target of a
/// `break` statement, the value returned by [RawIRWriter.nestingLevel] after
/// emitting the `block` instruction that should be targeted by the `break`
/// statement.
final breakStack = <int>[];
/// For each enclosing flow control construct that may be the target of a
/// `continue` statement, the value returned by [RawIRWriter.nestingLevel]
/// after emitting the `block` or `loop` instruction that should be targeted
/// by the `continue` statement.
final continueStack = <int>[];
/// For each unmatched `function` instruction that has been output, the value
/// returned by [RawIRWriter.nestingLevel] after emitting that `function`
/// instruction.
///
/// This is used to compute the appropriate parameter to the `br` instruction
/// when generating IR for a `return` statement.
final functionNestingStack = <int>[];
final ir = CodedIRWriter();
final Map<VariableElement, int> locals = {};
late final oneArgument = ir.encodeArgumentNames([null]);
late final twoArguments = ir.encodeArgumentNames([null, null]);
late final null_ = ir.encodeLiteral(null);
late final one = ir.encodeLiteral(1);
late final stackIndices101 = ir.encodeStackIndices(const [1, 0, 1]);
_AstToIRVisitor(
{required this.typeSystem,
required this.typeProvider,
required this.eventListener})
: coreLibrary = typeProvider.objectElement.library;
/// If [node] is used as the target of a [CompoundAssignmentExpression],
/// returns the [CompoundAssignmentExpression].
CompoundAssignmentExpression? assignmentTargeting(AstNode node) {
while (true) {
var parent = node.parent!;
switch (parent) {
case PrefixedIdentifier() when identical(node, parent.identifier):
case PropertyAccess() when identical(node, parent.propertyName):
node = parent;
case AssignmentExpression() when identical(node, parent.leftHandSide):
return parent;
case PostfixExpression(operator: Token(:var type))
when type == TokenType.PLUS_PLUS || type == TokenType.MINUS_MINUS:
return parent;
case PrefixExpression(operator: Token(:var type))
when type == TokenType.PLUS_PLUS || type == TokenType.MINUS_MINUS:
return parent;
case dynamic(:var runtimeType):
throw UnimplementedError('TODO(paulberry): $runtimeType');
}
}
}
/// Visits L-value [node] and returns the templates for reading/writing it.
_LValueTemplates dispatchLValue(Expression node) => node.accept(this)!;
/// Visits [node], reporting progress to [eventListener].
///
/// If [node] has null shorting behavior, then [terminateNullShorting]
/// determines how the null shorting behavior is handled. If
/// [terminateNullShorting] is `true` (the default), then null shorting will
/// be terminated after visiting [node], by emitting an `end` instruction;
/// this means that in the case where the null short occurs, the expression
/// will evaluate to `null`. If [terminateNullShorting] is `false`, then null
/// shorting won't be terminated; this means that in the case where the null
/// short occurs, execution of the parent node will be skipped too.
void dispatchNode(AstNode node, {bool terminateNullShorting = true}) {
eventListener.onEnterNode(node);
var previousNestingLevel = ir.nestingLevel;
var lValueTemplates = node.accept(this);
// If the node was an L-value, then its visitor didn't actually perform the
// read, so do that now.
lValueTemplates?.simpleRead(this);
if (terminateNullShorting) ir.endTo(previousNestingLevel);
eventListener.onExitNode();
}
/// Called by [astToIR] after visiting the code to be analyzed.
CodedIRContainer finish() {
assert(breakStack.isEmpty);
assert(continueStack.isEmpty);
assert(functionNestingStack.isEmpty);
var result = CodedIRContainer(ir);
eventListener.onFinished(result);
return result;
}
void instanceCall(MethodElement? staticElement, String name,
List<DartType> typeArguments, ArgumentNamesRef argumentNames) {
if (staticElement == null) throw UnimplementedError('TODO(paulberry)');
ir.call(
ir.encodeCallDescriptor(
ElementCallDescriptor(staticElement, typeArguments: typeArguments)),
argumentNames);
}
void instanceGet(PropertyAccessorElement? staticElement, String name) {
if (staticElement == null) {
throw UnimplementedError('TODO(paulberry): dynamic instance get');
}
ir.call(ir.encodeCallDescriptor(ElementCallDescriptor(staticElement)),
oneArgument);
}
void instanceSet(PropertyAccessorElement? staticElement, String name) {
if (staticElement == null) {
throw UnimplementedError('TODO(paulberry): dynamic instance set');
}
ir.call(ir.encodeCallDescriptor(ElementCallDescriptor(staticElement)),
twoArguments);
}
MethodElement lookupToString(DartType? type) {
var class_ =
type is InterfaceType ? type.element : typeProvider.objectElement;
return class_.augmented
.lookUpMethod(name: 'toString', library: coreLibrary)!;
}
/// Performs a null check that is part of a null shorting expression.
///
/// If [nonNull] is `false` (the default), and the value at the top of the
/// stack is `null`, execution will branch to the end of the null shorting
/// expression, and the null shorting expression will evaluate to `null`.
/// Otherwise, execution will proceed normally.
///
/// If [nonNull] is `true`, and the value at the top of the stack is not
/// `null`, execution will branch to the end of the null shorting expression,
/// and the null shorting expression will evaluate to the value at the top of
/// the stack. Otherwise, execution will proceed normally.
///
/// [additionalDiscardDepth] specifies the number of additional stack values
/// (beyond the value that is null checked) which should be discarded if the
/// expression is null shorted.
///
/// [previousNestingLevel] is the value returned by [RawIRWriter.nestingLevel]
/// at the beginning of the null shorting expression. It is used to detect
/// whether null shorting has already been begun, and therefore whether a
/// `block` instruction needs to be output.
void nullShortingCheck(
{required int previousNestingLevel,
bool nonNull = false,
int additionalDiscardDepth = 0}) {
assert(previousNestingLevel <= ir.nestingLevel);
// Stack: value
ir.dup();
// Stack: value value
ir.literal(null_);
// Stack: value value null
ir.eq();
// Stack: value (value == null)
if (nonNull) {
ir.not();
// Stack: value (value != null)
}
if (previousNestingLevel == ir.nestingLevel) {
// Null shorting hasn't begun yet for the containing expression, so start
// it now by opening a block; the block will be ended at the end of the
// null shorting expression, so it will be the branch target for null
// shorts.
ir.block(2 + additionalDiscardDepth, 1);
// Stack: BLOCK(1) value (value == null)
}
ir.brIf(0);
// Stack: BLOCK(1)? value
}
Null this_() {
ir.readLocal(0); // Stack: this
}
@override
Null visitAdjacentStrings(AdjacentStrings node) {
for (var string in node.strings) {
dispatchNode(string);
// Stack: string
}
// Stack: strings
ir.concat(node.strings.length);
// Stack: result
}
@override
Null visitAssignmentExpression(AssignmentExpression node) {
var previousNestingLevel = ir.nestingLevel;
var lValueTemplates = dispatchLValue(node.leftHandSide);
// Stack: lValue
switch (node.operator.type) {
case TokenType.EQ:
dispatchNode(node.rightHandSide);
// Stack: lValue rhs
eventListener.onEnterNode(node.leftHandSide);
lValueTemplates.write(this);
// Stack: rhs
eventListener.onExitNode();
// Stack: result
case TokenType.QUESTION_QUESTION_EQ:
lValueTemplates.readForCompoundAssignment(this);
// Stack: lValue oldValue
nullShortingCheck(
previousNestingLevel: previousNestingLevel,
nonNull: true,
additionalDiscardDepth: lValueTemplates.subexpressionCount);
// Stack: BLOCK(1)? lvalue oldValue
ir.drop();
// Stack: BLOCK(1)? lvalue
dispatchNode(node.rightHandSide);
// Stack: lValue rhs
eventListener.onEnterNode(node.leftHandSide);
lValueTemplates.write(this);
// Stack: rhs
eventListener.onExitNode();
case TokenType.AMPERSAND_EQ:
case TokenType.BAR_EQ:
case TokenType.CARET_EQ:
case TokenType.GT_GT_EQ:
case TokenType.GT_GT_GT_EQ:
case TokenType.LT_LT_EQ:
case TokenType.MINUS_EQ:
case TokenType.PERCENT_EQ:
case TokenType.PLUS_EQ:
case TokenType.SLASH_EQ:
case TokenType.STAR_EQ:
case TokenType.TILDE_SLASH_EQ:
lValueTemplates.readForCompoundAssignment(this);
// Stack: lValue oldValue
dispatchNode(node.rightHandSide);
// Stack: lValue oldValue rhs
var lexeme = node.operator.lexeme;
assert(lexeme.endsWith('='));
instanceCall(node.staticElement, lexeme.substring(0, lexeme.length - 1),
const [], twoArguments);
// Stack: lValue newValue
eventListener.onEnterNode(node.leftHandSide);
lValueTemplates.write(this);
// Stack: newValue
eventListener.onExitNode();
// Stack: result
case var tokenType:
throw UnimplementedError('TODO(paulberry): $tokenType');
}
}
@override
Null visitAwaitExpression(AwaitExpression node) {
dispatchNode(node.expression);
// Stack: expression
if (!typeSystem.isSubtypeOf(
node.expression.staticType!, typeProvider.futureDynamicType)) {
throw UnimplementedError('TODO(paulberry): handle await of non-future');
}
ir.await_();
// Stack: result
}
@override
Null visitBinaryExpression(BinaryExpression node) {
var tokenType = node.operator.type;
switch (tokenType) {
case TokenType.EQ_EQ:
dispatchNode(node.leftOperand);
// Stack: lhs
dispatchNode(node.rightOperand);
// Stack: lhs rhs
ir.eq();
// Stack: (lhs == rhs)
case TokenType.BANG_EQ:
dispatchNode(node.leftOperand);
// Stack: lhs
dispatchNode(node.rightOperand);
// Stack: lhs rhs
ir.eq();
// Stack: (lhs == rhs)
ir.not();
// Stack: (lhs != rhs)
case TokenType.AMPERSAND_AMPERSAND:
ir.block(0, 1);
// Stack: BLOCK(1)
dispatchNode(node.leftOperand);
// Stack: BLOCK(1) lhs
ir.dup();
// Stack: BLOCK(1) lhs lhs
ir.not();
// Stack: BLOCK(1) lhs !lhs
ir.brIf(0);
// Stack: BLOCK(1) lhs
ir.drop();
// Stack: BLOCK(1)
dispatchNode(node.rightOperand);
// Stack: BLOCK(1) rhs
ir.end();
// Stack: result
case TokenType.BAR_BAR:
ir.block(0, 1);
// Stack: BLOCK(1)
dispatchNode(node.leftOperand);
// Stack: BLOCK(1) lhs
ir.dup();
// Stack: BLOCK(1) lhs lhs
ir.brIf(0);
// Stack: BLOCK(1) lhs
ir.drop();
// Stack: BLOCK(1)
dispatchNode(node.rightOperand);
// Stack: BLOCK(1) rhs
ir.end();
// Stack: result
case TokenType.QUESTION_QUESTION:
ir.block(0, 1);
// Stack: BLOCK(1)
dispatchNode(node.leftOperand);
// Stack: BLOCK(1) lhs
ir.dup();
// Stack: BLOCK(1) lhs lhs
ir.literal(null_);
// Stack: BLOCK(1) lhs lhs null
ir.eq();
// Stack: BLOCK(1) lhs (lhs == null)
ir.not();
// Stack: BLOCK(1) lhs (lhs != null)
ir.brIf(0);
// Stack: BLOCK(1) lhs
ir.drop();
// Stack: BLOCK(1)
dispatchNode(node.rightOperand);
// Stack: BLOCK(1) rhs
ir.end();
// Stack: result
case TokenType.AMPERSAND:
case TokenType.BAR:
case TokenType.CARET:
case TokenType.GT:
case TokenType.GT_EQ:
case TokenType.GT_GT:
case TokenType.GT_GT_GT:
case TokenType.LT:
case TokenType.LT_EQ:
case TokenType.LT_LT:
case TokenType.MINUS:
case TokenType.PERCENT:
case TokenType.PLUS:
case TokenType.SLASH:
case TokenType.STAR:
case TokenType.TILDE_SLASH:
dispatchNode(node.leftOperand);
// Stack: lhs
dispatchNode(node.rightOperand);
// Stack: lhs rhs
instanceCall(node.staticElement, tokenType.lexeme, [], twoArguments);
// Stack: result
default:
throw UnimplementedError('TODO(paulberry): $node');
}
}
@override
Null visitBlock(Block node) {
var previousLocalVariableCount = ir.localVariableCount;
for (var statement in node.statements) {
dispatchNode(statement);
}
ir.releaseTo(previousLocalVariableCount);
}
@override
Null visitBlockFunctionBody(BlockFunctionBody node) {
dispatchNode(node.block);
ir.literal(null_);
// Stack: null
}
@override
Null visitBooleanLiteral(BooleanLiteral node) {
ir.literal(ir.encodeLiteral(node.value));
// Stack: value
}
@override
Null visitBreakStatement(BreakStatement node) {
if (node.label != null) {
throw UnimplementedError('TODO(paulberry)');
}
ir.br(ir.nestingLevel - breakStack.last);
}
@override
Null visitConditionalExpression(ConditionalExpression node) {
ir.block(0, 1);
// Stack: BLOCK(1)
ir.block(0, 0);
// Stack: BLOCK(1) BLOCK(0)
dispatchNode(node.condition);
// Stack: BLOCK(1) BLOCK(0) condition
ir.not();
// Stack: BLOCK(1) BLOCK(0) !condition
ir.brIf(0);
// Stack: BLOCK(1) BLOCK(0)
dispatchNode(node.thenExpression);
// Stack: BLOCK(1) BLOCK(0) thenExpression
ir.br(1);
// Stack: BLOCK(1) BLOCK(0) indeterminate
ir.end();
// Stack: BLOCK(1)
dispatchNode(node.elseExpression);
// Stack: BLOCK(1) elseExpression
ir.end();
// Stack: result
}
@override
Null visitContinueStatement(ContinueStatement node) {
if (node.label != null) {
throw UnimplementedError('TODO(paulberry)');
}
ir.br(ir.nestingLevel - continueStack.last);
}
@override
Null visitDoStatement(DoStatement node) {
ir.block(0, 0);
// Stack: BLOCK(0)
breakStack.add(ir.nestingLevel);
ir.loop(0);
// Stack: BLOCK(0) LOOP(0)
ir.block(0, 0);
// Stack: BLOCK(0) LOOP(0) BLOCK(0)
continueStack.add(ir.nestingLevel);
dispatchNode(node.body);
// Stack: BLOCK(0) LOOP(0) BLOCK(0)
continueStack.removeLast();
ir.end();
// Stack: BLOCK(0) LOOP(0)
dispatchNode(node.condition);
// Stack: BLOCK(0) LOOP(0) condition
ir.not();
// Stack: BLOCK(0) LOOP(0) !condition
ir.brIf(1);
// Stack: BLOCK(0) LOOP(0)
ir.end();
// Stack: BLOCK(0) indeterminate
breakStack.removeLast();
ir.end();
// Stack: (empty)
}
@override
Null visitDoubleLiteral(DoubleLiteral node) {
ir.literal(ir.encodeLiteral(node.value));
// Stack: value
}
@override
Null visitExpressionFunctionBody(ExpressionFunctionBody node) {
dispatchNode(node.expression);
// Stack: expression
}
@override
Null visitExpressionStatement(ExpressionStatement node) {
dispatchNode(node.expression);
// Stack: expression
ir.drop();
// Stack: (empty)
}
@override
Null visitForStatement(ForStatement node) {
switch (node.forLoopParts) {
case ForParts(:var condition, :var updaters) && var forParts:
switch (forParts) {
case ForPartsWithDeclarations(:var variables):
dispatchNode(variables);
case dynamic(:var runtimeType):
throw UnimplementedError('TODO(paulberry): handle $runtimeType');
}
// Stack: (empty)
ir.block(0, 0);
// Stack: BLOCK(0)
breakStack.add(ir.nestingLevel);
ir.loop(0);
// Stack: BLOCK(0) LOOP(0)
if (condition != null) {
dispatchNode(condition);
// Stack: BLOCK(0) LOOP(0) condition
ir.not();
// Stack: BLOCK(0) LOOP(0) !condition
ir.brIf(1);
// Stack: BLOCK(0) LOOP(0)
}
ir.block(0, 0);
// Stack: BLOCK(0) LOOP(0) BLOCK(0)
continueStack.add(ir.nestingLevel);
dispatchNode(node.body);
// Stack: BLOCK(0) LOOP(0) BLOCK(0)
continueStack.removeLast();
ir.end();
// Stack: BLOCK(0) LOOP(0)
for (var updater in updaters) {
dispatchNode(updater);
// Stack: BLOCK(0) LOOP(0) updater
ir.drop();
// Stack: BLOCK(0) LOOP(0)
}
ir.end();
// Stack: BLOCK(0) indeterminate
breakStack.removeLast();
ir.end();
// Stack: (empty)
case dynamic(:var runtimeType):
throw UnimplementedError('TODO(paulberry): handle $runtimeType');
}
}
void visitFunctionBody(ExecutableElement element, FunctionBody body,
{required bool isInstanceMember}) {
int count = 0;
if (isInstanceMember) {
count++;
}
for (var element in element.parameters) {
assert(!locals.containsKey(element));
var localIndex = ir.localVariableCount + count;
locals[element] = localIndex;
count++;
}
var flags = FunctionFlags(
async: body.isAsynchronous,
generator: body.isGenerator,
instance: isInstanceMember);
ir.function(ir.encodeType(element.type), flags);
// Stack: FUNCTION(flags) parameters
functionNestingStack.add(ir.nestingLevel);
if (count > 0) {
ir.alloc(count);
// Stack: FUNCTION(flags) parameters
for (var i = 0; i++ < count;) {
ir.writeLocal(ir.localVariableCount - i);
}
// Stack: FUNCTION(flags)
}
dispatchNode(body);
// Stack: FUNCTION(flags) returnValue
if (count > 0) {
ir.release(count);
}
ir.end();
// Stack: (empty)
functionNestingStack.removeLast();
}
@override
Null visitIfStatement(IfStatement node) {
if (node.caseClause != null) throw UnimplementedError('TODO(paulberry)');
var elseStatement = node.elseStatement;
if (elseStatement == null) {
dispatchNode(node.expression);
// Stack: expression
ir.not();
// Stack: !expression
ir.block(1, 0);
// Stack: BLOCK(0) !expression
ir.brIf(0);
// Stack: BLOCK(0)
dispatchNode(node.thenStatement);
ir.end();
// Stack: (empty)
} else {
dispatchNode(node.expression);
// Stack: expression
ir.not();
// Stack: !expression
ir.block(1, 0);
// Stack: BLOCK(0) !expression
ir.block(1, 0);
// Stack: BLOCK(0) BLOCK(0) !expression
ir.brIf(0);
// Stack: BLOCK(0) BLOCK(0)
dispatchNode(node.thenStatement);
ir.br(1);
// Stack: BLOCK(0) BLOCK(0) indeterminate
ir.end();
// Stack: BLOCK(0)
dispatchNode(elseStatement);
ir.end();
// Stack: (empty)
}
}
@override
Null visitIntegerLiteral(IntegerLiteral node) {
// TODO(paulberry): do we need to handle out of range integers?
ir.literal(ir.encodeLiteral(node.value!));
// Stack: value
}
@override
Null visitInterpolationExpression(InterpolationExpression node) {
dispatchNode(node.expression);
// Stack: expression
instanceCall(lookupToString(node.expression.staticType), 'toString', [],
oneArgument);
// Stack: expression.toString()
}
@override
Null visitInterpolationString(InterpolationString node) {
ir.literal(ir.encodeLiteral(node.value));
// Stack: value
}
@override
Null visitIsExpression(IsExpression node) {
dispatchNode(node.expression);
// Stack: expression
ir.is_(ir.encodeType(node.type.type!));
// Stack: (expression is type)
if (node.notOperator != null) {
ir.not();
// Stack: (expression is! type)
}
}
@override
Null visitMethodInvocation(MethodInvocation node) {
var previousNestingLevel = ir.nestingLevel;
var argumentNames = <String?>[];
var target = node.target;
var methodElement = node.methodName.staticElement;
switch (methodElement) {
case FunctionElement(enclosingElement: CompilationUnitElement()):
assert(!node.isNullAware);
_handleInvocationArgs(
argumentList: node.argumentList,
argumentNames: argumentNames,
isNullAware: false,
previousNestingLevel: previousNestingLevel);
// Stack: arguments
if (methodElement.library.isDartCore &&
methodElement.name == 'identical') {
ir.identical();
} else {
ir.call(
ir.encodeCallDescriptor(ElementCallDescriptor(methodElement,
typeArguments: node.typeArgumentTypes!)),
ir.encodeArgumentNames(argumentNames));
}
// Stack: result
case MethodElement(isStatic: false):
if (target == null) {
assert(!node.isNullAware);
this_();
// Stack: this
} else {
dispatchNode(target, terminateNullShorting: false);
// Stack: target
}
argumentNames.add(null);
_handleInvocationArgs(
argumentList: node.argumentList,
argumentNames: argumentNames,
isNullAware: node.isNullAware,
previousNestingLevel: previousNestingLevel);
// Stack: BLOCK(1)? target arguments
instanceCall(methodElement, node.methodName.name,
node.typeArgumentTypes!, ir.encodeArgumentNames(argumentNames));
// Stack: BLOCK(1)? result
case MethodElement(isStatic: true):
assert(!node.isNullAware);
_handleInvocationArgs(
argumentList: node.argumentList,
argumentNames: argumentNames,
isNullAware: false,
previousNestingLevel: previousNestingLevel);
// Stack: arguments
ir.call(
ir.encodeCallDescriptor(ElementCallDescriptor(methodElement,
typeArguments: node.typeArgumentTypes!)),
ir.encodeArgumentNames(argumentNames));
// Stack: result
case dynamic(:var runtimeType):
throw UnimplementedError(
'TODO(paulberry): $runtimeType: $methodElement');
}
}
@override
Null visitNullLiteral(NullLiteral node) {
ir.literal(null_);
// Stack: null
}
@override
Null visitParenthesizedExpression(ParenthesizedExpression node) {
dispatchNode(node.expression);
// Stack: expression
}
@override
Null visitPostfixExpression(PostfixExpression node) {
switch (node.operator.type) {
case TokenType.PLUS_PLUS:
case TokenType.MINUS_MINUS:
var lValueTemplates = dispatchLValue(node.operand);
// Stack: lValue
eventListener.onEnterNode(node.operand);
lValueTemplates.readForPostfixIncDec(this);
// Stack: oldValue lValue oldValue
eventListener.onExitNode();
ir.literal(one);
// Stack: oldValue lValue oldValue 1
instanceCall(
node.staticElement, node.operator.lexeme[0], [], twoArguments);
// Stack: oldValue lValue newValue
lValueTemplates.write(this);
// Stack: oldValue newValue
ir.drop();
// Stack: oldValue
default:
throw UnimplementedError('TODO(paulberry): ${node.operator.type}');
}
}
@override
_LValueTemplates? visitPrefixedIdentifier(PrefixedIdentifier node) {
var prefix = node.prefix;
var prefixElement = prefix.staticElement;
switch (prefixElement) {
case ParameterElement():
case LocalVariableElement():
dispatchNode(prefix);
// Stack: prefix
return _PropertyAccessTemplates(node.identifier);
case dynamic(:var runtimeType):
throw UnimplementedError(
'TODO(paulberry): $runtimeType: $prefixElement');
}
}
@override
Null visitPrefixExpression(PrefixExpression node) {
switch (node.operator.type) {
case TokenType.BANG:
dispatchNode(node.operand);
// Stack: operand
ir.not();
// Stack: !operand
case TokenType.PLUS_PLUS:
case TokenType.MINUS_MINUS:
var lValueTemplates = dispatchLValue(node.operand);
// Stack: lValue
lValueTemplates.readForCompoundAssignment(this);
// Stack: lValue oldValue
ir.literal(one);
// Stack: lValue oldValue 1
instanceCall(
node.staticElement, node.operator.lexeme[0], [], twoArguments);
// Stack: lValue newValue
eventListener.onEnterNode(node.operand);
lValueTemplates.write(this);
// Stack: newValue
eventListener.onExitNode();
default:
throw UnimplementedError('TODO(paulberry): ${node.operator.type}');
}
}
@override
_LValueTemplates visitPropertyAccess(PropertyAccess node) {
var previousNestingLevel = ir.nestingLevel;
// TODO(paulberry): handle cascades
dispatchNode(node.target!, terminateNullShorting: false);
// Stack: target
if (node.isNullAware) {
nullShortingCheck(previousNestingLevel: previousNestingLevel);
}
// Stack: BLOCK(1)? target
return _PropertyAccessTemplates(node.propertyName);
}
@override
Null visitReturnStatement(ReturnStatement node) {
switch (node.expression) {
case null:
ir.literal(null_);
case var expression:
dispatchNode(expression);
}
// Stack: returnValue
ir.br(ir.nestingLevel - functionNestingStack.last);
// Stack: indeterminate
}
@override
_LValueTemplates visitSimpleIdentifier(SimpleIdentifier node) {
var staticElement = node.staticElement;
if (staticElement == null) {
if (assignmentTargeting(node) case var assignment?) {
staticElement = assignment.readElement ?? assignment.writeElement;
}
}
switch (staticElement) {
case ParameterElement():
case LocalVariableElement():
return _LocalTemplates(locals[staticElement]!);
case PropertyAccessorElement(isStatic: false):
this_();
// Stack: this
return _PropertyAccessTemplates(node);
// Stack: value
case dynamic(:var runtimeType):
throw UnimplementedError(
'TODO(paulberry): $runtimeType: $staticElement');
}
}
@override
Null visitSimpleStringLiteral(SimpleStringLiteral node) {
ir.literal(ir.encodeLiteral(node.value));
// Stack: value
}
@override
Null visitStringInterpolation(StringInterpolation node) {
for (var element in node.elements) {
dispatchNode(element);
}
// Stack: strings
ir.concat(node.elements.length);
// Stack: result
}
@override
Null visitThisExpression(ThisExpression node) => this_();
@override
Null visitVariableDeclarationList(VariableDeclarationList variables) {
for (var VariableDeclaration(:initializer, :declaredElement!)
in variables.variables) {
assert(!locals.containsKey(declaredElement));
var localIndex = ir.localVariableCount;
locals[declaredElement] = localIndex;
ir.alloc(1);
if (initializer != null) {
dispatchNode(initializer);
// Stack: initializer
ir.writeLocal(localIndex);
// Stack: (empty)
} else if (typeSystem.isNullable(declaredElement.type)) {
ir.literal(null_);
// Stack: null
ir.writeLocal(localIndex);
// Stack: (empty)
}
}
}
@override
Null visitVariableDeclarationStatement(VariableDeclarationStatement node) {
if (node.variables.isLate) {
throw UnimplementedError(
'TODO(paulberry): handle late variable declarations');
}
dispatchNode(node.variables);
}
@override
Null visitWhileStatement(WhileStatement node) {
ir.block(0, 0);
// Stack: BLOCK(0)
breakStack.add(ir.nestingLevel);
ir.loop(0);
// Stack: BLOCK(0) LOOP(0)
continueStack.add(ir.nestingLevel);
dispatchNode(node.condition);
// Stack: BLOCK(0) LOOP(0) condition
ir.not();
// Stack: BLOCK(0) LOOP(0) !condition
ir.brIf(1);
// Stack: BLOCK(0) LOOP(0)
dispatchNode(node.body);
// Stack: BLOCK(0) LOOP(0)
continueStack.removeLast();
ir.end();
// Stack: BLOCK(0) indeterminate
breakStack.removeLast();
ir.end();
// Stack: (empty)
}
@override
Null visitYieldStatement(YieldStatement node) {
dispatchNode(node.expression);
// Stack: expression
ir.yield_();
// Stack: (empty)
}
void _handleInvocationArgs(
{required ArgumentList argumentList,
required List<String?> argumentNames,
required bool isNullAware,
required int previousNestingLevel}) {
if (isNullAware) {
nullShortingCheck(previousNestingLevel: previousNestingLevel);
}
// Stack: BLOCK(1)? target
for (var argument in argumentList.arguments) {
if (argument is NamedExpression) {
dispatchNode(argument.expression);
argumentNames.add(argument.name.label.name);
} else {
dispatchNode(argument);
argumentNames.add(null);
}
}
}
}
/// Instruction templates for converting a local variable reference to IR.
class _LocalTemplates extends _LValueTemplates {
final int localIndex;
_LocalTemplates(this.localIndex) : super(subexpressionCount: 0);
void read(_AstToIRVisitor visitor) {
visitor.ir.readLocal(localIndex);
// Stack: value
}
@override
void readForCompoundAssignment(_AstToIRVisitor visitor) {
read(visitor);
// Stack: value
}
@override
void readForPostfixIncDec(_AstToIRVisitor visitor) {
read(visitor);
// Stack: value
visitor.ir.dup();
// Stack: value value
}
@override
void simpleRead(_AstToIRVisitor visitor) {
read(visitor);
// Stack: value
}
@override
void write(_AstToIRVisitor visitor) {
// Stack: value
visitor.ir.dup();
// Stack: value value
visitor.ir.writeLocal(localIndex);
// Stack: value
}
}
/// Instruction templates for converting an L-value to IR.
///
/// If the L-value has subexpressions, then at the time [_LValueTemplates] is
/// constructed, instructions should be emitted that place the values of these
/// subexpressions on the stack.
///
/// The methods in this class will emit instructions manipulate those
/// subexpression values in order to read or write the L-value. These methods
/// are abstract, and are defined in a derived class for each specific kind of
/// L-value supported by Dart.
sealed class _LValueTemplates {
final int subexpressionCount;
_LValueTemplates({required this.subexpressionCount});
/// Outputs the IR instructions for reading from the L-value in a way that
/// remains prepared for a compound assignment.
///
/// On entry, the stack contents should be the subexpression values.
///
/// On exit, the stack contents will be the subexpression values, followed by
/// the result of the read operation. (This allows the caller to implement a
/// compound assignment by modifying the value at the top of the stack and
/// then making a follow-up call to [write]).
void readForCompoundAssignment(_AstToIRVisitor visitor);
/// Outputs the IR instructions for reading from the L-value in a way that
/// remains prepared for a postfix increment or decrement.
///
/// On entry, the stack contents should be the subexpression values.
///
/// On exit, the stack contents will be the result of the read operation,
/// followed by the subexpression values, followed by the result of the read
/// operation again. (This allows the caller to implement a postfix increment
/// or decrement by modifying the value at the top of the stack, then making a
/// follow-up call to [write], then dropping the result of the write so that
/// the result of the read operation remains).
void readForPostfixIncDec(_AstToIRVisitor visitor);
/// Outputs the IR instructions for a simple read of the L-value.
///
/// On entry, the stack contents should be the subexpression values.
///
/// On exit, the stack contents will be the result of the read operation.
void simpleRead(_AstToIRVisitor visitor);
/// Outputs the IR instructions for writing to the L-value.
///
/// On entry, the stack contents should be the subexpression values.
///
/// On exit, the stack contents will be the value that was written.
void write(_AstToIRVisitor visitor);
}
/// Instruction templates for converting a property access to IR.
class _PropertyAccessTemplates extends _LValueTemplates {
final SimpleIdentifier property;
/// Creates a property access template.
///
/// Caller is responsible for ensuring that the target of the property access
/// is pushed to the stack.
_PropertyAccessTemplates(this.property) : super(subexpressionCount: 1);
void read(_AstToIRVisitor visitor) {
// Stack: target
visitor.instanceGet(
(property.staticElement ??
visitor.assignmentTargeting(property)?.readElement)
as PropertyAccessorElement?,
property.name);
// Stack: value
}
@override
void readForCompoundAssignment(_AstToIRVisitor visitor) {
// Stack: target
visitor.ir.dup();
// Stack: target target
read(visitor);
// Stack: target value
}
@override
void readForPostfixIncDec(_AstToIRVisitor visitor) {
// Stack: target
visitor.ir.dup();
// Stack: target target
read(visitor);
// Stack: target value
visitor.ir.shuffle(2, visitor.stackIndices101);
// Stack: value target value
}
@override
void simpleRead(_AstToIRVisitor visitor) {
// Stack: target
read(visitor);
// Stack: value
}
@override
void write(_AstToIRVisitor visitor) {
// Stack: target value
visitor.ir.shuffle(2, visitor.stackIndices101);
// Stack: value target value
visitor.instanceSet(
visitor.assignmentTargeting(property)!.writeElement
as PropertyAccessorElement?,
property.name);
// Stack: value returnValue
visitor.ir.drop();
// Stack: value
}
}