blob: 72086bfd641893d7d123ad7b017c436050c717cf [file] [log] [blame]
// Copyright (c) 2013, 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 ssa.tracer;
import '../../compiler_new.dart' show OutputSink;
import '../diagnostics/invariant.dart' show DEBUG_MODE;
import '../inferrer/abstract_value_domain.dart';
import '../js_backend/namer.dart' show suffixForGetInterceptor;
import '../tracer.dart';
import '../world.dart' show JClosedWorld;
import 'nodes.dart';
/// Outputs SSA code in a format readable by Hydra IR.
/// Tracing is disabled by default, see ../tracer.dart for how
/// to enable it.
class HTracer extends HGraphVisitor with TracerUtil {
final JClosedWorld closedWorld;
@override
final OutputSink output;
HTracer(this.output, this.closedWorld);
void traceGraph(String name, HGraph graph) {
DEBUG_MODE = true;
tag("cfg", () {
printProperty("name", name);
visitDominatorTree(graph);
});
}
void addPredecessors(HBasicBlock block) {
if (block.predecessors.isEmpty) {
printEmptyProperty("predecessors");
} else {
addIndent();
add("predecessors");
for (HBasicBlock predecessor in block.predecessors) {
add(' "B${predecessor.id}"');
}
add("\n");
}
}
void addSuccessors(HBasicBlock block) {
if (block.successors.isEmpty) {
printEmptyProperty("successors");
} else {
addIndent();
add("successors");
for (HBasicBlock successor in block.successors) {
add(' "B${successor.id}"');
}
add("\n");
}
}
void addInstructions(
HInstructionStringifier stringifier, HInstructionList list) {
for (HInstruction instruction = list.first;
instruction != null;
instruction = instruction.next) {
int bci = 0;
int uses = instruction.usedBy.length;
String changes = instruction.sideEffects.hasSideEffects() ? '!' : ' ';
String depends = instruction.sideEffects.dependsOnSomething() ? '?' : '';
addIndent();
String temporaryId = stringifier.temporaryId(instruction);
String instructionString = stringifier.visit(instruction);
add("$bci $uses $temporaryId $instructionString $changes $depends <|@\n");
}
}
@override
void visitBasicBlock(HBasicBlock block) {
HInstructionStringifier stringifier =
new HInstructionStringifier(block, closedWorld);
assert(block.id != null);
tag("block", () {
printProperty("name", "B${block.id}");
printProperty("from_bci", -1);
printProperty("to_bci", -1);
addPredecessors(block);
addSuccessors(block);
printEmptyProperty("xhandlers");
printEmptyProperty("flags");
if (block.dominator != null) {
printProperty("dominator", "B${block.dominator.id}");
}
tag("states", () {
tag("locals", () {
printProperty("size", 0);
printProperty("method", "None");
block.forEachPhi((phi) {
String phiId = stringifier.temporaryId(phi);
StringBuffer inputIds = new StringBuffer();
for (int i = 0; i < phi.inputs.length; i++) {
inputIds.write(stringifier.temporaryId(phi.inputs[i]));
inputIds.write(" ");
}
println("${phi.id} $phiId [ $inputIds]");
});
});
});
tag("HIR", () {
addInstructions(stringifier, block.phis);
addInstructions(stringifier, block);
});
});
}
}
class HInstructionStringifier implements HVisitor<String> {
final JClosedWorld closedWorld;
final HBasicBlock currentBlock;
HInstructionStringifier(this.currentBlock, this.closedWorld);
AbstractValueDomain get _abstractValueDomain =>
closedWorld.abstractValueDomain;
visit(HInstruction node) => '${node.accept(this)} ${node.instructionType}';
String temporaryId(HInstruction instruction) {
String prefix;
if (instruction.isNull(_abstractValueDomain).isDefinitelyTrue) {
prefix = 'u';
} else if (instruction
.isConflicting(_abstractValueDomain)
.isDefinitelyTrue) {
prefix = 'c';
} else if (instruction
.isExtendableArray(_abstractValueDomain)
.isDefinitelyTrue) {
prefix = 'e';
} else if (instruction
.isFixedArray(_abstractValueDomain)
.isDefinitelyTrue) {
prefix = 'f';
} else if (instruction
.isMutableArray(_abstractValueDomain)
.isDefinitelyTrue) {
prefix = 'm';
} else if (instruction.isArray(_abstractValueDomain).isDefinitelyTrue) {
prefix = 'a';
} else if (instruction.isString(_abstractValueDomain).isDefinitelyTrue) {
prefix = 's';
} else if (instruction
.isIndexablePrimitive(_abstractValueDomain)
.isDefinitelyTrue) {
prefix = 'r';
} else if (instruction.isBoolean(_abstractValueDomain).isDefinitelyTrue) {
prefix = 'b';
} else if (instruction.isInteger(_abstractValueDomain).isDefinitelyTrue) {
prefix = 'i';
} else if (instruction.isDouble(_abstractValueDomain).isDefinitelyTrue) {
prefix = 'd';
} else if (instruction.isNumber(_abstractValueDomain).isDefinitelyTrue) {
prefix = 'n';
} else if (_abstractValueDomain
.containsAll(instruction.instructionType)
.isPotentiallyTrue) {
prefix = 'v';
} else {
prefix = 'U';
}
return "$prefix${instruction.id}";
}
@override
String visitLateValue(HLateValue node) {
return "LateValue: ${temporaryId(node.inputs[0])}";
}
String handleInvokeBinary(HInvokeBinary node, String opcode) {
String left = temporaryId(node.left);
String right = temporaryId(node.right);
return '$opcode: $left $right';
}
@override
String visitAbs(HAbs node) {
String operand = temporaryId(node.operand);
return "Abs: $operand";
}
@override
String visitAdd(HAdd node) => handleInvokeBinary(node, 'Add');
@override
String visitBitAnd(HBitAnd node) => handleInvokeBinary(node, 'BitAnd');
@override
String visitBitNot(HBitNot node) {
String operand = temporaryId(node.operand);
return "BitNot: $operand";
}
@override
String visitBitOr(HBitOr node) => handleInvokeBinary(node, 'BitOr');
@override
String visitBitXor(HBitXor node) => handleInvokeBinary(node, 'BitXor');
@override
String visitBoundsCheck(HBoundsCheck node) {
String lengthId = temporaryId(node.length);
String indexId = temporaryId(node.index);
return "BoundsCheck: length = $lengthId, index = $indexId";
}
@override
String visitBreak(HBreak node) {
HBasicBlock target = currentBlock.successors[0];
if (node.label != null) {
return "Break ${node.label.labelName}: (B${target.id})";
}
return "Break: (B${target.id})";
}
@override
String visitConstant(HConstant constant) => "Constant: ${constant.constant}";
@override
String visitContinue(HContinue node) {
HBasicBlock target = currentBlock.successors[0];
if (node.label != null) {
return "Continue ${node.label.labelName}: (B${target.id})";
}
return "Continue: (B${target.id})";
}
@override
String visitCreate(HCreate node) {
return handleGenericInvoke("Create", "${node.element.name}", node.inputs);
}
@override
String visitCreateBox(HCreateBox node) {
return handleGenericInvoke("CreateBox", "", node.inputs);
}
@override
String visitDivide(HDivide node) => handleInvokeBinary(node, 'Divide');
@override
String visitExit(HExit node) => "Exit";
@override
String visitFieldGet(HFieldGet node) {
if (node.isNullCheck) {
return 'FieldGet: NullCheck ${temporaryId(node.receiver)}';
}
String fieldName = node.element.name;
return 'FieldGet: ${temporaryId(node.receiver)}.$fieldName';
}
@override
String visitFieldSet(HFieldSet node) {
String valueId = temporaryId(node.value);
String fieldName = node.element.name;
return 'FieldSet: ${temporaryId(node.receiver)}.$fieldName to $valueId';
}
@override
String visitReadModifyWrite(HReadModifyWrite node) {
String fieldName = node.element.name;
String receiverId = temporaryId(node.receiver);
String op = node.jsOp;
if (node.isAssignOp) {
String valueId = temporaryId(node.value);
return 'ReadModifyWrite: $receiverId.$fieldName $op= $valueId';
} else if (node.isPreOp) {
return 'ReadModifyWrite: $op$receiverId.$fieldName';
} else {
return 'ReadModifyWrite: $receiverId.$fieldName$op';
}
}
@override
String visitGetLength(HGetLength node) {
return 'GetLength: ${temporaryId(node.receiver)}';
}
@override
String visitLocalGet(HLocalGet node) {
String localName = node.variable.name;
return 'LocalGet: ${temporaryId(node.local)}.$localName';
}
@override
String visitLocalSet(HLocalSet node) {
String valueId = temporaryId(node.value);
String localName = node.variable.name;
return 'LocalSet: ${temporaryId(node.local)}.$localName to $valueId';
}
@override
String visitGoto(HGoto node) {
HBasicBlock target = currentBlock.successors[0];
return "Goto: (B${target.id})";
}
@override
String visitGreater(HGreater node) => handleInvokeBinary(node, 'Greater');
@override
String visitGreaterEqual(HGreaterEqual node) {
return handleInvokeBinary(node, 'GreaterEqual');
}
@override
String visitIdentity(HIdentity node) => handleInvokeBinary(node, 'Identity');
@override
String visitIf(HIf node) {
HBasicBlock thenBlock = currentBlock.successors[0];
HBasicBlock elseBlock = currentBlock.successors[1];
String conditionId = temporaryId(node.inputs[0]);
return "If ($conditionId): (B${thenBlock.id}) else (B${elseBlock.id})";
}
String handleGenericInvoke(
String invokeType, String functionName, List<HInstruction> arguments) {
StringBuffer argumentsString = new StringBuffer();
for (int i = 0; i < arguments.length; i++) {
if (i != 0) argumentsString.write(", ");
argumentsString.write(temporaryId(arguments[i]));
}
return "$invokeType: $functionName($argumentsString)";
}
@override
String visitIndex(HIndex node) {
String receiver = temporaryId(node.receiver);
String index = temporaryId(node.index);
return "Index: $receiver[$index]";
}
@override
String visitIndexAssign(HIndexAssign node) {
String receiver = temporaryId(node.receiver);
String index = temporaryId(node.index);
String value = temporaryId(node.value);
return "IndexAssign: $receiver[$index] = $value";
}
@override
String visitInterceptor(HInterceptor node) {
String value = temporaryId(node.inputs[0]);
if (node.interceptedClasses != null) {
String cls = suffixForGetInterceptor(closedWorld.commonElements,
closedWorld.nativeData, node.interceptedClasses);
return "Interceptor (${cls}): $value";
}
return "Interceptor: $value";
}
@override
String visitInvokeClosure(HInvokeClosure node) =>
handleInvokeDynamic(node, "InvokeClosure");
String handleInvokeDynamic(HInvokeDynamic invoke, String kind) {
String receiver = temporaryId(invoke.receiver);
String name = invoke.selector.name;
String target = "$receiver.$name";
int offset = HInvoke.ARGUMENTS_OFFSET;
List arguments = invoke.inputs.sublist(offset);
return handleGenericInvoke(kind, target, arguments) +
"(${invoke.receiverType})";
}
@override
String visitInvokeDynamicMethod(HInvokeDynamicMethod node) =>
handleInvokeDynamic(node, "InvokeDynamicMethod");
@override
String visitInvokeDynamicGetter(HInvokeDynamicGetter node) =>
handleInvokeDynamic(node, "InvokeDynamicGetter");
@override
String visitInvokeDynamicSetter(HInvokeDynamicSetter node) =>
handleInvokeDynamic(node, "InvokeDynamicSetter");
@override
String visitInvokeStatic(HInvokeStatic invoke) {
String target = invoke.element.name;
return handleGenericInvoke("InvokeStatic", target, invoke.inputs);
}
@override
String visitInvokeSuper(HInvokeSuper invoke) {
String target = invoke.element.name;
return handleGenericInvoke("InvokeSuper", target, invoke.inputs);
}
@override
String visitInvokeConstructorBody(HInvokeConstructorBody invoke) {
String target = invoke.element.name;
return handleGenericInvoke("InvokeConstructorBody", target, invoke.inputs);
}
@override
String visitInvokeGeneratorBody(HInvokeGeneratorBody invoke) {
String target = invoke.element.name;
return handleGenericInvoke("InvokeGeneratorBody", target, invoke.inputs);
}
@override
String visitForeignCode(HForeignCode node) {
var template = node.codeTemplate;
String code = '${template.ast}';
var inputs = node.inputs.map(temporaryId).join(', ');
return "ForeignCode: $code ($inputs)";
}
@override
String visitLess(HLess node) => handleInvokeBinary(node, 'Less');
@override
String visitLessEqual(HLessEqual node) =>
handleInvokeBinary(node, 'LessEqual');
@override
String visitLiteralList(HLiteralList node) {
StringBuffer elementsString = new StringBuffer();
for (int i = 0; i < node.inputs.length; i++) {
if (i != 0) elementsString.write(", ");
elementsString.write(temporaryId(node.inputs[i]));
}
return "LiteralList: [$elementsString]";
}
@override
String visitLoopBranch(HLoopBranch branch) {
HBasicBlock bodyBlock = currentBlock.successors[0];
HBasicBlock exitBlock = currentBlock.successors[1];
String conditionId = temporaryId(branch.inputs[0]);
return "LoopBranch ($conditionId): (B${bodyBlock.id}) then (B${exitBlock.id})";
}
@override
String visitMultiply(HMultiply node) => handleInvokeBinary(node, 'Multiply');
@override
String visitNegate(HNegate node) {
String operand = temporaryId(node.operand);
return "Negate: $operand";
}
@override
String visitNot(HNot node) => "Not: ${temporaryId(node.inputs[0])}";
@override
String visitParameterValue(HParameterValue node) {
return "ParameterValue: ${node.sourceElement.name}";
}
@override
String visitLocalValue(HLocalValue node) {
return "LocalValue: ${node.sourceElement.name}";
}
@override
String visitPhi(HPhi phi) {
StringBuffer buffer = new StringBuffer();
buffer.write("Phi: ");
for (int i = 0; i < phi.inputs.length; i++) {
if (i > 0) buffer.write(", ");
buffer.write(temporaryId(phi.inputs[i]));
}
return buffer.toString();
}
@override
String visitRef(HRef node) {
return 'Ref: ${temporaryId(node.value)}';
}
@override
String visitReturn(HReturn node) => "Return: ${temporaryId(node.inputs[0])}";
@override
String visitShiftLeft(HShiftLeft node) =>
handleInvokeBinary(node, 'ShiftLeft');
@override
String visitShiftRight(HShiftRight node) =>
handleInvokeBinary(node, 'ShiftRight');
@override
String visitStatic(HStatic node) => "Static: ${node.element.name}";
@override
String visitLazyStatic(HLazyStatic node) =>
"LazyStatic: ${node.element.name}";
@override
String visitOneShotInterceptor(HOneShotInterceptor node) =>
handleInvokeDynamic(node, "OneShotInterceptor");
@override
String visitStaticStore(HStaticStore node) {
String lhs = node.element.name;
return "StaticStore: $lhs = ${temporaryId(node.inputs[0])}";
}
@override
String visitStringConcat(HStringConcat node) {
var leftId = temporaryId(node.left);
var rightId = temporaryId(node.right);
return "StringConcat: $leftId + $rightId";
}
@override
String visitStringify(HStringify node) {
return "Stringify: ${temporaryId(node.inputs[0])}";
}
@override
String visitSubtract(HSubtract node) => handleInvokeBinary(node, 'Subtract');
@override
String visitSwitch(HSwitch node) {
StringBuffer buf = new StringBuffer();
buf.write("Switch: (");
buf.write(temporaryId(node.inputs[0]));
buf.write(") ");
for (int i = 1; i < node.inputs.length; i++) {
buf.write(temporaryId(node.inputs[i]));
buf.write(": B");
buf.write(node.block.successors[i - 1].id);
buf.write(", ");
}
buf.write("default: B");
buf.write(node.defaultTarget.id);
return buf.toString();
}
@override
String visitThis(HThis node) => "This";
@override
String visitThrow(HThrow node) => "Throw: ${temporaryId(node.inputs[0])}";
@override
String visitThrowExpression(HThrowExpression node) {
return "ThrowExpression: ${temporaryId(node.inputs[0])}";
}
@override
String visitTruncatingDivide(HTruncatingDivide node) {
return handleInvokeBinary(node, 'TruncatingDivide');
}
@override
String visitRemainder(HRemainder node) {
return handleInvokeBinary(node, 'Remainder');
}
@override
String visitExitTry(HExitTry node) {
return "ExitTry";
}
@override
String visitTry(HTry node) {
List<HBasicBlock> successors = currentBlock.successors;
String tryBlock = 'B${successors[0].id}';
String catchBlock = 'none';
if (node.catchBlock != null) {
catchBlock = 'B${successors[1].id}';
}
String finallyBlock = 'none';
if (node.finallyBlock != null) {
finallyBlock = 'B${node.finallyBlock.id}';
}
return "Try: $tryBlock, Catch: $catchBlock, Finally: $finallyBlock, "
"Join: B${successors.last.id}";
}
@override
String visitIs(HIs node) {
String type = node.typeExpression.toString();
return "Is: ${temporaryId(node.expression)} is $type";
}
@override
String visitIsViaInterceptor(HIsViaInterceptor node) {
String type = node.typeExpression.toString();
return "IsViaInterceptor: ${temporaryId(node.inputs[0])} is $type";
}
@override
String visitTypeConversion(HTypeConversion node) {
String checkedInput = temporaryId(node.checkedInput);
String rest;
if (node.inputs.length == 2) {
rest = " ${temporaryId(node.inputs.last)}";
} else {
assert(node.inputs.length == 1);
rest = "";
}
String kind = _typeConversionKind(node);
return "TypeConversion: $kind $checkedInput to ${node.instructionType}$rest";
}
String _typeConversionKind(HTypeConversion node) {
if (node.isTypeCheck) return 'TYPE_CHECK';
if (node.isCastCheck) return 'CAST_CHECK';
return '?';
}
@override
String visitPrimitiveCheck(HPrimitiveCheck node) {
String checkedInput = temporaryId(node.checkedInput);
assert(node.inputs.length == 1);
String kind = _primitiveCheckKind(node);
return "PrimitiveCheck: $kind $checkedInput to ${node.instructionType}";
}
String _primitiveCheckKind(HPrimitiveCheck node) {
if (node.isReceiverTypeCheck) return 'RECEIVER';
if (node.isArgumentTypeCheck) return 'ARGUMENT';
return '?';
}
@override
String visitBoolConversion(HBoolConversion node) {
String checkedInput = temporaryId(node.checkedInput);
return "BoolConversion: $checkedInput";
}
@override
String visitTypeKnown(HTypeKnown node) {
assert(node.inputs.length <= 2);
String result =
"TypeKnown: ${temporaryId(node.checkedInput)} is ${node.knownType}";
if (node.witness != null) {
result += " witnessed by ${temporaryId(node.witness)}";
}
return result;
}
@override
String visitRangeConversion(HRangeConversion node) {
return "RangeConversion: ${node.checkedInput}";
}
@override
String visitTypeInfoReadRaw(HTypeInfoReadRaw node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "TypeInfoReadRaw: $inputs";
}
@override
String visitTypeInfoReadVariable(HTypeInfoReadVariable node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "TypeInfoReadVariable: ${node.variable} $inputs";
}
@override
String visitTypeInfoExpression(HTypeInfoExpression node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "TypeInfoExpression: ${node.kindAsString} ${node.dartType}"
" ($inputs)";
}
@override
String visitAwait(HAwait node) {
return "Await: ${temporaryId(node.inputs[0])}";
}
@override
String visitYield(HYield node) {
return "Yield${node.hasStar ? "*" : ""}: ${temporaryId(node.inputs[0])}";
}
@override
String visitIsTest(HIsTest node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "IsTest: $inputs";
}
@override
String visitAsCheck(HAsCheck node) {
var inputs = node.inputs.map(temporaryId).join(', ');
String error = node.isTypeError ? 'TypeError' : 'CastError';
return "AsCheck: $error $inputs";
}
@override
String visitAsCheckSimple(HAsCheckSimple node) {
var inputs = node.inputs.map(temporaryId).join(', ');
String error = node.isTypeError ? 'TypeError' : 'CastError';
return "AsCheckSimple: $error ${node.dartType} $inputs";
}
@override
String visitSubtypeCheck(HSubtypeCheck node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "SubtypeCheck: $inputs";
}
@override
String visitLoadType(HLoadType node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "LoadType: ${node.typeExpression} $inputs";
}
@override
String visitInstanceEnvironment(HInstanceEnvironment node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "InstanceEnvironment: $inputs";
}
@override
String visitTypeEval(HTypeEval node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "TypeEval: ${node.typeExpression} ${node.envStructure} $inputs";
}
@override
String visitTypeBind(HTypeBind node) {
var inputs = node.inputs.map(temporaryId).join(', ');
return "TypeBind: $inputs";
}
}