haikuwebkit/JSTests/controlFlowProfiler/brace-location.js

160 lines
6.8 KiB
JavaScript
Raw Permalink Normal View History

Adjust the ranges of basic block statements in JSC's control flow profiler to be mutually exclusive https://bugs.webkit.org/show_bug.cgi?id=141095 Reviewed by Mark Lam. Suppose the control flow of a program forms basic block A with successor block B. A's end offset will be the *same* as B's start offset in the current architecture of the control flow profiler. This makes reasoning about the text offsets of the control flow profiler unsound. To make reasoning about offsets sound, all basic block ranges should be mutually exclusive. All calls to emitProfileControlFlow now pass in the *start* of a basic block as the text offset argument. This simplifies all calls to emitProfileControlFlow because the previous implementation had a lot of edge cases for getting the desired basic block text boundaries. This patch also ensures that the basic block boundary of a block statement is the exactly the block's open and close brace offsets (inclusive). For example, in if/for/while statements. This also has the consequence that for statements like "if (cond) foo();", the whitespace preceding "foo()" is not part of the "foo()" basic block, but instead is part of the "if (cond) " basic block. This is okay because these text offsets aren't meant to be human readable. Instead, they reflect the text offsets of JSC's AST nodes. The Web Inspector is the only client of this API and user of these text offsets and it is not negatively effected by this new behavior. * bytecode/CodeBlock.cpp: (JSC::CodeBlock::insertBasicBlockBoundariesForControlFlowProfiler): When computing basic block boundaries in CodeBlock, we ensure that every block's end offset is one less than its successor's start offset to maintain that boundaries' ranges should be mutually exclusive. * bytecompiler/BytecodeGenerator.cpp: (JSC::BytecodeGenerator::BytecodeGenerator): Because the control flow profiler needs to know which functions have executed, we can't lazily create functions. This was a bug from before that was hidden because the Type Profiler was always enabled when the control flow profiler was enabled when profiling was turned on from the Web Inspector. But, JSC allows for Control Flow profiling to be turned on without Type Profiling, so we need to ensure the Control Flow profiler has all the data it needs. * bytecompiler/NodesCodegen.cpp: (JSC::ConditionalNode::emitBytecode): (JSC::IfElseNode::emitBytecode): (JSC::WhileNode::emitBytecode): (JSC::ForNode::emitBytecode): (JSC::ForInNode::emitMultiLoopBytecode): (JSC::ForOfNode::emitBytecode): (JSC::TryNode::emitBytecode): * jsc.cpp: (functionHasBasicBlockExecuted): We now assert that the substring argument is indeed a substring of the function argument's text because subtle bugs could be introduced otherwise. * parser/ASTBuilder.h: (JSC::ASTBuilder::setStartOffset): * parser/Nodes.h: (JSC::Node::setStartOffset): * parser/Parser.cpp: (JSC::Parser<LexerType>::parseBlockStatement): (JSC::Parser<LexerType>::parseStatement): (JSC::Parser<LexerType>::parseMemberExpression): For the various function call AST nodes, their m_position member variable is now the start of the entire function call expression and not at the start of the open paren of the arguments list. * runtime/BasicBlockLocation.cpp: (JSC::BasicBlockLocation::getExecutedRanges): * runtime/ControlFlowProfiler.cpp: (JSC::ControlFlowProfiler::getBasicBlocksForSourceID): Function ranges inserted as gaps should follow the same criteria that the bytecode generator uses to ensure that basic blocks start and end offsets are mutually exclusive. * tests/controlFlowProfiler/brace-location.js: Added. (foo): (bar): (baz): (testIf): (testForRegular): (testForIn): (testForOf): (testWhile): (testIfNoBraces): (testForRegularNoBraces): (testForInNoBraces): (testForOfNoBraces): (testWhileNoBraces): * tests/controlFlowProfiler/conditional-expression.js: Added. (foo): (bar): (baz): (testConditionalBasic): (testConditionalFunctionCall): * tests/controlFlowProfiler/driver/driver.js: (checkBasicBlock): Canonical link: https://commits.webkit.org/159941@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@180518 268f45cc-cd09-0410-ab3c-d52691b4dbfc
2015-02-23 22:10:51 +00:00
load("./driver/driver.js");
function foo() {}
function bar() {}
function baz() {}
function testIf(x) {
if (x < 10) { foo(); } else if (x < 20) { bar(); } else { baz() }
}
testIf(9);
// Note, the check will be against the basic block that contains the first matched character.
// So in this following case, the block that contains the '{'.
checkBasicBlock(testIf, "{ foo", ShouldHaveExecuted);
// In this case, it will test the basic block that contains the ' ' character.
checkBasicBlock(testIf, " foo", ShouldHaveExecuted);
checkBasicBlock(testIf, "} else if", ShouldHaveExecuted);
checkBasicBlock(testIf, "else if", ShouldNotHaveExecuted);
checkBasicBlock(testIf, "{ bar", ShouldNotHaveExecuted);
checkBasicBlock(testIf, " bar", ShouldNotHaveExecuted);
checkBasicBlock(testIf, "else {", ShouldNotHaveExecuted);
checkBasicBlock(testIf, "{ baz", ShouldNotHaveExecuted);
checkBasicBlock(testIf, " baz", ShouldNotHaveExecuted);
testIf(21);
checkBasicBlock(testIf, "else if (x < 20)", ShouldHaveExecuted);
checkBasicBlock(testIf, "{ bar", ShouldNotHaveExecuted);
checkBasicBlock(testIf, " bar", ShouldNotHaveExecuted);
checkBasicBlock(testIf, "else {", ShouldHaveExecuted);
checkBasicBlock(testIf, "{ baz", ShouldHaveExecuted);
checkBasicBlock(testIf, " baz", ShouldHaveExecuted);
testIf(11);
checkBasicBlock(testIf, "{ bar", ShouldHaveExecuted);
checkBasicBlock(testIf, " bar", ShouldHaveExecuted);
function testForRegular(x) {
for (var i = 0; i < x; i++) { foo(); } bar();
}
testForRegular(0);
checkBasicBlock(testForRegular, "{ foo", ShouldNotHaveExecuted);
checkBasicBlock(testForRegular, "} bar", ShouldNotHaveExecuted);
checkBasicBlock(testForRegular, " bar", ShouldHaveExecuted);
testForRegular(1);
checkBasicBlock(testForRegular, "{ foo", ShouldHaveExecuted);
checkBasicBlock(testForRegular, "} bar", ShouldHaveExecuted);
function testForIn(x) {
for (var i in x) { foo(); } bar();
}
testForIn({});
checkBasicBlock(testForIn, "{ foo", ShouldNotHaveExecuted);
checkBasicBlock(testForIn, "} bar", ShouldNotHaveExecuted);
checkBasicBlock(testForIn, " bar", ShouldHaveExecuted);
testForIn({foo: 20});
checkBasicBlock(testForIn, "{ foo", ShouldHaveExecuted);
checkBasicBlock(testForIn, "} bar", ShouldHaveExecuted);
function testForOf(x) {
for (var i of x) { foo(); } bar();
}
testForOf([]);
checkBasicBlock(testForOf, "{ foo", ShouldNotHaveExecuted);
checkBasicBlock(testForOf, " foo", ShouldNotHaveExecuted);
checkBasicBlock(testForOf, "} bar", ShouldNotHaveExecuted);
checkBasicBlock(testForOf, " bar", ShouldHaveExecuted);
testForOf([20]);
checkBasicBlock(testForOf, "{ foo", ShouldHaveExecuted);
checkBasicBlock(testForOf, "} bar", ShouldHaveExecuted);
function testWhile(x) {
var i = 0; while (i++ < x) { foo(); } bar();
}
testWhile(0);
checkBasicBlock(testWhile, "{ foo", ShouldNotHaveExecuted);
checkBasicBlock(testWhile, " foo", ShouldNotHaveExecuted);
checkBasicBlock(testWhile, "} bar", ShouldNotHaveExecuted);
checkBasicBlock(testWhile, " bar", ShouldHaveExecuted);
testWhile(1);
checkBasicBlock(testWhile, "{ foo", ShouldHaveExecuted);
checkBasicBlock(testWhile, "} bar", ShouldHaveExecuted);
// No braces tests.
function testIfNoBraces(x) {
if (x < 10) foo(); else if (x < 20) bar(); else baz();
}
testIfNoBraces(9);
checkBasicBlock(testIfNoBraces, "foo", ShouldHaveExecuted);
checkBasicBlock(testIfNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testIfNoBraces, "; else if", ShouldHaveExecuted);
checkBasicBlock(testIfNoBraces, " else if", ShouldNotHaveExecuted);
checkBasicBlock(testIfNoBraces, " bar", ShouldNotHaveExecuted);
checkBasicBlock(testIfNoBraces, "bar", ShouldNotHaveExecuted);
checkBasicBlock(testIfNoBraces, "else baz", ShouldNotHaveExecuted);
checkBasicBlock(testIfNoBraces, "baz", ShouldNotHaveExecuted);
testIfNoBraces(21);
checkBasicBlock(testIfNoBraces, "else baz", ShouldHaveExecuted);
checkBasicBlock(testIfNoBraces, "baz", ShouldHaveExecuted);
checkBasicBlock(testIfNoBraces, "; else baz", ShouldNotHaveExecuted);
checkBasicBlock(testIfNoBraces, "else if (x < 20)", ShouldHaveExecuted);
// Note that the whitespace preceding bar is part of the previous basic block.
// An if statement's if-true basic block text offset begins at the start offset
// of the if-true block, in this case, just the expression "bar()".
checkBasicBlock(testIfNoBraces, " bar", ShouldHaveExecuted);
checkBasicBlock(testIfNoBraces, "bar", ShouldNotHaveExecuted);
testIfNoBraces(11);
checkBasicBlock(testIfNoBraces, " bar", ShouldHaveExecuted);
checkBasicBlock(testIfNoBraces, "bar", ShouldHaveExecuted);
function testForRegularNoBraces(x) {
for (var i = 0; i < x; i++) foo(); bar();
}
testForRegularNoBraces(0);
checkBasicBlock(testForRegularNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testForRegularNoBraces, "foo", ShouldNotHaveExecuted);
checkBasicBlock(testForRegularNoBraces, "; bar", ShouldNotHaveExecuted);
checkBasicBlock(testForRegularNoBraces, " bar", ShouldHaveExecuted);
testForRegularNoBraces(1);
checkBasicBlock(testForRegularNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testForRegularNoBraces, "foo", ShouldHaveExecuted);
checkBasicBlock(testForRegularNoBraces, " bar", ShouldHaveExecuted);
function testForInNoBraces(x) {
for (var i in x) foo(); bar();
}
testForInNoBraces({});
checkBasicBlock(testForInNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testForInNoBraces, "foo", ShouldNotHaveExecuted);
checkBasicBlock(testForInNoBraces, "; bar", ShouldNotHaveExecuted);
checkBasicBlock(testForInNoBraces, " bar", ShouldHaveExecuted);
testForInNoBraces({foo: 20});
checkBasicBlock(testForInNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testForInNoBraces, "foo", ShouldHaveExecuted);
checkBasicBlock(testForInNoBraces, "; bar", ShouldHaveExecuted);
function testForOfNoBraces(x) {
for (var i of x) foo(); bar();
}
testForOfNoBraces([]);
checkBasicBlock(testForOfNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testForOfNoBraces, "foo", ShouldNotHaveExecuted);
checkBasicBlock(testForOfNoBraces, "; bar", ShouldNotHaveExecuted);
checkBasicBlock(testForOfNoBraces, " bar", ShouldHaveExecuted);
testForOfNoBraces([20]);
checkBasicBlock(testForOfNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testForOfNoBraces, "foo", ShouldHaveExecuted);
checkBasicBlock(testForOfNoBraces, "; bar", ShouldHaveExecuted);
function testWhileNoBraces(x) {
var i = 0; while (i++ < x) foo(); bar();
}
testWhileNoBraces(0);
checkBasicBlock(testWhileNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testWhileNoBraces, "foo", ShouldNotHaveExecuted);
checkBasicBlock(testWhileNoBraces, "; bar", ShouldNotHaveExecuted);
checkBasicBlock(testWhileNoBraces, " bar", ShouldHaveExecuted);
testWhileNoBraces(1);
checkBasicBlock(testWhileNoBraces, " foo", ShouldHaveExecuted);
checkBasicBlock(testWhileNoBraces, "foo", ShouldHaveExecuted);
checkBasicBlock(testWhileNoBraces, "; bar", ShouldHaveExecuted);