blob: 185f0119738d7ea6fd550bcc3b7c4f5283115b50 [file] [log] [blame]
// Copyright (c) 2014, 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.
library dart2js.ir_nodes_sexpr;
import '../constants/values.dart';
import '../util/util.dart';
import 'cps_ir_nodes.dart';
import '../universe/call_structure.dart' show CallStructure;
/// A [Decorator] is a function used by [SExpressionStringifier] to augment the
/// output produced for a node or reference. It can be provided to the
/// constructor.
typedef String Decorator(node, String s);
/// Generate a Lisp-like S-expression representation of an IR node as a string.
class SExpressionStringifier extends Indentation implements Visitor<String> {
final _Namer namer = new _Namer();
String newValueName(Primitive node) => namer.nameValue(node);
String newContinuationName(Continuation node) => namer.nameContinuation(node);
Decorator decorator;
SExpressionStringifier([this.decorator]) {
if (this.decorator == null) {
this.decorator = (node, String s) => s;
}
}
/// Create a stringifier with an extra layer of decoration.
SExpressionStringifier withDecorator(Decorator subDecorator) {
return new SExpressionStringifier((node, String s) {
return subDecorator(node, decorator(node, s));
});
}
/// Create a stringifier that displays type information.
SExpressionStringifier withTypes() => withDecorator(typeDecorator);
/// Creates a stringifier that adds annotations from a map;
/// see [Node.debugString].
SExpressionStringifier withAnnotations(Map annotations) {
return withDecorator(decoratorFromMap(annotations));
}
static Decorator decoratorFromMap(Map annotations) {
Map<Node, String> nodeMap = {};
for (var key in annotations.keys) {
if (key is Node) {
nodeMap[key] = '${annotations[key]}';
} else {
String text = key;
Node node = annotations[key];
if (nodeMap.containsKey(node)) {
// In case two annotations belong to the same node,
// put both annotations on that node.
nodeMap[node] += ' $text';
} else {
nodeMap[node] = text;
}
}
}
return (node, string) {
String text = nodeMap[node];
if (text != null) return '***$string*** $text';
return string;
};
}
static String typeDecorator(node, String string) {
return node is Variable ? '$string:${node.type}' : string;
}
String access(Reference<Definition> r) {
if (r == null) return '**** NULL ****';
return decorator(r, namer.getName(r.definition));
}
String optionalAccess(Reference<Definition> reference) {
return reference == null ? '()' : '(${access(reference)})';
}
String visitParameter(Parameter node) {
return namer.nameParameter(node);
}
String visitMutableVariable(MutableVariable node) {
return namer.nameMutableVariable(node);
}
/// Main entry point for creating a [String] from a [Node]. All recursive
/// calls must go through this method.
String visit(Node node) {
if (node == null) return '**** NULL ****';
String s = node.accept(this);
return decorator(node, s);
}
String formatOptionalParameter(Parameter parameter) {
return parameter == null ? '()' : '(${visit(parameter)})';
}
String visitFunctionDefinition(FunctionDefinition node) {
String name = node.element.name;
String interceptorParameter =
formatOptionalParameter(node.interceptorParameter);
String thisParameter = formatOptionalParameter(node.receiverParameter);
String parameters = node.parameters.map(visit).join(' ');
namer.setReturnContinuation(node.returnContinuation);
String body = indentBlock(() => visit(node.body));
return '$indentation'
'(FunctionDefinition $name $interceptorParameter $thisParameter '
'($parameters) return\n'
'$body)';
}
String visitLetPrim(LetPrim node) {
String name = newValueName(node.primitive);
String value = visit(node.primitive);
String bindings = '($name $value)';
String skip = ' ' * '(LetPrim ('.length;
while (node.body is LetPrim) {
node = node.body;
name = newValueName(node.primitive);
value = visit(node.primitive);
String binding = decorator(node, '($name $value)');
bindings += '\n${indentation}$skip$binding';
}
String body = indentBlock(() => visit(node.body));
return '$indentation(LetPrim ($bindings)\n$body)';
}
bool isBranchTarget(Continuation cont) {
return cont.hasExactlyOneUse && cont.firstRef.parent is Branch;
}
String visitLetCont(LetCont node) {
String conts;
bool first = true;
String skip = ' ' * '(LetCont ('.length;
for (Continuation continuation in node.continuations) {
// Branch continuations will be printed at their use site.
if (isBranchTarget(continuation)) continue;
if (first) {
first = false;
conts = visit(continuation);
} else {
// Each subsequent line is indented additional spaces to align it
// with the previous continuation.
conts += '\n${indentation}$skip${visit(continuation)}';
}
}
// If there were no continuations printed, just print the body.
if (first) return visit(node.body);
String body = indentBlock(() => visit(node.body));
return '$indentation(LetCont ($conts)\n$body)';
}
String visitLetHandler(LetHandler node) {
// There are no explicit references to the handler, so we leave it
// anonymous in the printed representation.
String parameters = node.handler.parameters
.map((p) => '${decorator(p, newValueName(p))}')
.join(' ');
String handlerBody =
indentBlock(() => indentBlock(() => visit(node.handler.body)));
String body = indentBlock(() => visit(node.body));
return '$indentation(LetHandler (($parameters)\n$handlerBody)\n$body)';
}
String visitLetMutable(LetMutable node) {
String name = visit(node.variable);
String value = access(node.valueRef);
String body = indentBlock(() => visit(node.body));
return '$indentation(LetMutable ($name $value)\n$body)';
}
String formatArguments(
CallStructure call, List<Reference<Primitive>> arguments,
[CallingConvention callingConvention = CallingConvention.Normal]) {
int positionalArgumentCount = call.positionalArgumentCount;
List<String> args =
arguments.take(positionalArgumentCount).map(access).toList();
List<String> argumentNames = call.getOrderedNamedArguments();
for (int i = 0; i < argumentNames.length; ++i) {
String name = argumentNames[i];
String arg = access(arguments[positionalArgumentCount + i]);
args.add("($name: $arg)");
}
// Constructors can have type parameter after the named arguments.
args.addAll(arguments
.skip(positionalArgumentCount + argumentNames.length)
.map(access));
return '(${args.join(' ')})';
}
String visitInvokeStatic(InvokeStatic node) {
String name = node.target.name;
String args =
formatArguments(node.selector.callStructure, node.argumentRefs);
return '(InvokeStatic $name $args)';
}
String visitInvokeMethod(InvokeMethod node) {
String name = node.selector.name;
String interceptor = optionalAccess(node.interceptorRef);
String receiver = access(node.receiverRef);
String arguments = formatArguments(
node.selector.callStructure, node.argumentRefs, node.callingConvention);
return '(InvokeMethod $interceptor $receiver $name $arguments)';
}
String visitInvokeMethodDirectly(InvokeMethodDirectly node) {
String interceptor = optionalAccess(node.interceptorRef);
String receiver = access(node.receiverRef);
String name = node.selector.name;
String arguments = formatArguments(
node.selector.callStructure, node.argumentRefs, node.callingConvention);
return '(InvokeMethodDirectly $interceptor $receiver $name $arguments)';
}
String visitInvokeConstructor(InvokeConstructor node) {
// TODO(karlklose): for illegal nodes constructed for tests or unresolved
// constructor calls in the DartBackend, we get an element with no enclosing
// class. Clean this up by introducing a name field to the node and
// removing [ErroneousElement]s from the IR.
String name = node.dartType != null
? node.dartType.toString()
: node.target.enclosingClass.name;
if (!node.target.name.isEmpty) {
name = '${name}.${node.target.name}';
}
String args =
formatArguments(node.selector.callStructure, node.argumentRefs);
return '(InvokeConstructor $name $args)';
}
String visitInvokeContinuation(InvokeContinuation node) {
String name = access(node.continuationRef);
if (node.isRecursive) name = 'rec $name';
String args = node.argumentRefs == null
? '**** NULL ****'
: node.argumentRefs.map(access).join(' ');
String escaping = node.isEscapingTry ? ' escape' : '';
return '$indentation(InvokeContinuation $name ($args)$escaping)';
}
String visitThrow(Throw node) {
String value = access(node.valueRef);
return '$indentation(Throw $value)';
}
String visitRethrow(Rethrow node) {
return '$indentation(Rethrow)';
}
String visitBranch(Branch node) {
String condition = access(node.conditionRef);
assert(isBranchTarget(node.trueContinuation));
assert(isBranchTarget(node.falseContinuation));
String trueCont = indentBlock(() => visit(node.trueContinuation));
String falseCont = indentBlock(() => visit(node.falseContinuation));
String strict = node.isStrictCheck ? 'Strict' : 'NonStrict';
return '$indentation(Branch $strict $condition\n$trueCont\n$falseCont)';
}
String visitUnreachable(Unreachable node) {
return '$indentation(Unreachable)';
}
String visitConstant(Constant node) {
String value = node.value.accept(new ConstantStringifier(), null);
return '(Constant $value)';
}
String visitContinuation(Continuation node) {
if (isBranchTarget(node)) {
assert(node.parameters.isEmpty);
assert(!node.isRecursive);
return indentBlock(() => visit(node.body));
}
String name = newContinuationName(node);
if (node.isRecursive) name = 'rec $name';
// TODO(karlklose): this should be changed to `.map(visit).join(' ')`
// and should recurse to [visit]. Currently we can't do that, because
// the unstringifier_test produces [LetConts] with dummy arguments on
// them.
String parameters = node.parameters
.map((p) => '${decorator(p, newValueName(p))}')
.join(' ');
String body = indentBlock(() => indentBlock(() => visit(node.body)));
return '($name ($parameters)\n$body)';
}
String visitGetMutable(GetMutable node) {
return '(GetMutable ${access(node.variableRef)})';
}
String visitSetMutable(SetMutable node) {
String value = access(node.valueRef);
return '(SetMutable ${access(node.variableRef)} $value)';
}
String visitTypeCast(TypeCast node) {
String value = access(node.valueRef);
String typeArguments = node.typeArgumentRefs.map(access).join(' ');
return '(TypeCast $value ${node.dartType} ($typeArguments))';
}
String visitTypeTest(TypeTest node) {
String value = access(node.valueRef);
String typeArguments = node.typeArgumentRefs.map(access).join(' ');
return '(TypeTest $value ${node.dartType} ($typeArguments))';
}
String visitTypeTestViaFlag(TypeTestViaFlag node) {
String interceptor = access(node.interceptorRef);
return '(TypeTestViaFlag $interceptor ${node.dartType})';
}
String visitLiteralList(LiteralList node) {
String values = node.valueRefs.map(access).join(' ');
return '(LiteralList ($values))';
}
String visitSetField(SetField node) {
String object = access(node.objectRef);
String field = node.field.name;
String value = access(node.valueRef);
return '(SetField $object $field $value)';
}
String visitGetField(GetField node) {
String object = access(node.objectRef);
String field = node.field.name;
return '(GetField $object $field)';
}
String visitGetStatic(GetStatic node) {
String element = node.element.name;
return '(GetStatic $element)';
}
String visitSetStatic(SetStatic node) {
String element = node.element.name;
String value = access(node.valueRef);
return '(SetStatic $element $value)';
}
String visitGetLazyStatic(GetLazyStatic node) {
String element = node.element.name;
return '(GetLazyStatic $element)';
}
String visitCreateBox(CreateBox node) {
return '(CreateBox)';
}
String visitCreateInstance(CreateInstance node) {
String className = node.classElement.name;
String arguments = node.argumentRefs.map(access).join(' ');
String typeInformation = optionalAccess(node.typeInformationRef);
return '(CreateInstance $className ($arguments) ($typeInformation))';
}
String visitInterceptor(Interceptor node) {
return '(Interceptor ${access(node.inputRef)})';
}
String visitReifyRuntimeType(ReifyRuntimeType node) {
return '(ReifyRuntimeType ${access(node.valueRef)})';
}
String visitReadTypeVariable(ReadTypeVariable node) {
return '(ReadTypeVariable ${access(node.targetRef)}.${node.variable})';
}
String visitTypeExpression(TypeExpression node) {
String args = node.argumentRefs.map(access).join(' ');
return '(TypeExpression ${node.kindAsString} ${node.dartType} ($args))';
}
String visitCreateInvocationMirror(CreateInvocationMirror node) {
String selector = node.selector.name;
String args = node.argumentRefs.map(access).join(' ');
return '(CreateInvocationMirror $selector ($args))';
}
String visitApplyBuiltinOperator(ApplyBuiltinOperator node) {
String operator = node.operator.toString();
String args = node.argumentRefs.map(access).join(' ');
return '(ApplyBuiltinOperator $operator ($args))';
}
String visitApplyBuiltinMethod(ApplyBuiltinMethod node) {
String method = node.method.toString();
String receiver = access(node.receiverRef);
String args = node.argumentRefs.map(access).join(' ');
return '(ApplyBuiltinMethod $method $receiver ($args))';
}
String visitForeignCode(ForeignCode node) {
String arguments = node.argumentRefs.map(access).join(' ');
return '(JS "${node.codeTemplate.source}" ($arguments))';
}
String visitGetLength(GetLength node) {
String object = access(node.objectRef);
return '(GetLength $object)';
}
String visitGetIndex(GetIndex node) {
String object = access(node.objectRef);
String index = access(node.indexRef);
return '(GetIndex $object $index)';
}
String visitSetIndex(SetIndex node) {
String object = access(node.objectRef);
String index = access(node.indexRef);
String value = access(node.valueRef);
return '(SetIndex $object $index $value)';
}
@override
String visitAwait(Await node) {
String value = access(node.inputRef);
return '(Await $value)';
}
@override
String visitYield(Yield node) {
String value = access(node.inputRef);
return '(Yield $value)';
}
String visitRefinement(Refinement node) {
String value = access(node.value);
return '(Refinement $value ${node.type})';
}
String visitBoundsCheck(BoundsCheck node) {
String object = access(node.objectRef);
String index = optionalAccess(node.indexRef);
String length = optionalAccess(node.lengthRef);
return '(BoundsCheck $object $index $length ${node.checkString})';
}
String visitReceiverCheck(ReceiverCheck node) {
String value = access(node.valueRef);
String condition = optionalAccess(node.conditionRef);
return '(ReceiverCheck $value ${node.selector} $condition '
'${node.flagString}))';
}
}
class ConstantStringifier extends ConstantValueVisitor<String, Null> {
// Some of these methods are unimplemented because we haven't had a need
// to print such constants. When printing is implemented, the corresponding
// parsing support should be added to SExpressionUnstringifier.parseConstant
// in the dart2js tests (currently in the file
// tests/compiler/dart2js/backend_dart/sexpr_unstringifier.dart).
String _failWith(ConstantValue constant) {
throw 'Stringification not supported for ${constant.toStructuredString()}';
}
String visitFunction(FunctionConstantValue constant, _) {
return '(Function "${constant.unparse()}")';
}
String visitNull(NullConstantValue constant, _) {
return '(Null)';
}
String visitInt(IntConstantValue constant, _) {
return '(Int ${constant.unparse()})';
}
String visitDouble(DoubleConstantValue constant, _) {
return '(Double ${constant.unparse()})';
}
String visitBool(BoolConstantValue constant, _) {
return '(Bool ${constant.unparse()})';
}
String visitString(StringConstantValue constant, _) {
return '(String ${constant.unparse()})';
}
String visitList(ListConstantValue constant, _) {
String entries =
constant.entries.map((entry) => entry.accept(this, _)).join(' ');
return '(List $entries)';
}
String visitMap(MapConstantValue constant, _) {
List<String> elements = <String>[];
for (int i = 0; i < constant.keys.length; ++i) {
ConstantValue key = constant.keys[i];
ConstantValue value = constant.values[i];
elements.add('(${key.accept(this, _)} . ${value.accept(this, _)})');
}
return '(Map (${elements.join(' ')}))';
}
String visitConstructed(ConstructedConstantValue constant, _) {
return '(Constructed "${constant.unparse()}")';
}
String visitType(TypeConstantValue constant, _) {
return '(Type "${constant.representedType}")';
}
String visitInterceptor(InterceptorConstantValue constant, _) {
return '(Interceptor "${constant.unparse()}")';
}
String visitSynthetic(SyntheticConstantValue constant, _) {
return '(Synthetic "${constant.unparse()}")';
}
String visitDeferred(DeferredConstantValue constant, _) {
return _failWith(constant);
}
}
class _Namer {
final Map<Node, String> _names = <Node, String>{};
int _valueCounter = 0;
int _continuationCounter = 0;
// TODO(sra): Make the methods not assert and print something indicating an
// error, so printer can be used to inspect broken terms.
String nameParameter(Parameter parameter) {
assert(!_names.containsKey(parameter));
String name =
parameter.hint != null ? parameter.hint.name : nameValue(parameter);
return _names[parameter] = name;
}
String nameMutableVariable(MutableVariable variable) {
assert(!_names.containsKey(variable));
return _names[variable] = variable.hint.name;
}
String nameContinuation(Continuation node) {
assert(!_names.containsKey(node));
return _names[node] = 'k${_continuationCounter++}';
}
String nameValue(Primitive node) {
assert(!_names.containsKey(node));
return _names[node] = 'v${_valueCounter++}';
}
void setReturnContinuation(Continuation node) {
assert(!_names.containsKey(node) || _names[node] == 'return');
_names[node] = 'return';
}
String getName(Node node) {
if (!_names.containsKey(node)) return 'MISSING_NAME';
return _names[node];
}
}