[turbofan] Simplify context specialization and fix for OSR.

AstGraphBuilder puts a constant context in from the beginning.
Also fix bug in merging contexts in environment.

R=mstarzinger@chromium.org
BUG=

Review URL: https://codereview.chromium.org/934293002

Cr-Commit-Position: refs/heads/master@{#26745}
This commit is contained in:
titzer 2015-02-19 03:36:38 -08:00 committed by Commit bot
parent 4c082b570d
commit d050c331eb
16 changed files with 303 additions and 107 deletions

View File

@ -379,7 +379,6 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info,
globals_(0, local_zone),
execution_control_(nullptr),
execution_context_(nullptr),
function_context_(nullptr),
input_buffer_size_(0),
input_buffer_(nullptr),
exit_control_(nullptr),
@ -399,9 +398,10 @@ Node* AstGraphBuilder::GetFunctionClosure() {
}
Node* AstGraphBuilder::GetFunctionContext() {
DCHECK(function_context_ != nullptr);
return function_context_;
void AstGraphBuilder::CreateFunctionContext(bool constant_context) {
function_context_.set(constant_context
? jsgraph()->HeapConstant(info()->context())
: NewOuterContextParam());
}
@ -420,7 +420,7 @@ Node* AstGraphBuilder::NewCurrentContextOsrValue() {
}
bool AstGraphBuilder::CreateGraph() {
bool AstGraphBuilder::CreateGraph(bool constant_context) {
Scope* scope = info()->scope();
DCHECK(graph() != NULL);
@ -442,8 +442,8 @@ bool AstGraphBuilder::CreateGraph() {
}
// Initialize the incoming context.
function_context_ = NewOuterContextParam();
ContextScope incoming(this, scope, function_context_);
CreateFunctionContext(constant_context);
ContextScope incoming(this, scope, function_context_.get());
// Build receiver check for sloppy mode if necessary.
// TODO(mstarzinger/verwaest): Should this be moved back into the CallIC?
@ -456,7 +456,8 @@ bool AstGraphBuilder::CreateGraph() {
if (heap_slots > 0) {
// Push a new inner context scope for the function.
Node* closure = GetFunctionClosure();
Node* inner_context = BuildLocalFunctionContext(function_context_, closure);
Node* inner_context =
BuildLocalFunctionContext(function_context_.get(), closure);
ContextScope top_context(this, scope, inner_context);
CreateGraphBody();
} else {
@ -2760,10 +2761,9 @@ Node* AstGraphBuilder::BuildLoadBuiltinsObject() {
Node* AstGraphBuilder::BuildLoadGlobalObject() {
Node* context = GetFunctionContext();
const Operator* load_op =
javascript()->LoadContext(0, Context::GLOBAL_OBJECT_INDEX, true);
return NewNode(load_op, context);
return NewNode(load_op, function_context_.get());
}
@ -3023,6 +3023,7 @@ void AstGraphBuilder::UpdateControlDependencyToLeaveFunction(Node* exit) {
void AstGraphBuilder::Environment::Merge(Environment* other) {
DCHECK(values_.size() == other->values_.size());
// TODO(titzer): make context stack heights match.
DCHECK(contexts_.size() <= other->contexts_.size());
// Nothing to do if the other environment is dead.
@ -3037,6 +3038,10 @@ void AstGraphBuilder::Environment::Merge(Environment* other) {
graph()->NewNode(common()->Merge(1), arraysize(inputs), inputs, true);
effect_dependency_ = other->effect_dependency_;
values_ = other->values_;
// TODO(titzer): make context stack heights match.
size_t min = std::min(contexts_.size(), other->contexts_.size());
contexts_ = other->contexts_;
contexts_.resize(min, nullptr);
return;
}

View File

@ -31,7 +31,7 @@ class AstGraphBuilder : public AstVisitor {
LoopAssignmentAnalysis* loop_assignment = NULL);
// Creates a graph by visiting the entire AST.
bool CreateGraph();
bool CreateGraph(bool constant_context);
// Helpers to create new control nodes.
Node* NewIfTrue() { return NewNode(common()->IfTrue()); }
@ -51,12 +51,6 @@ class AstGraphBuilder : public AstVisitor {
// Visiting function for declarations list is overridden.
void VisitDeclarations(ZoneList<Declaration*>* declarations) OVERRIDE;
// Get the node that represents the outer function context.
Node* GetFunctionContext();
// Get the node that represents the outer function closure.
Node* GetFunctionClosure();
private:
class AstContext;
class AstEffectContext;
@ -88,7 +82,7 @@ class AstGraphBuilder : public AstVisitor {
// Nodes representing values in the activation record.
SetOncePointer<Node> function_closure_;
Node* function_context_;
SetOncePointer<Node> function_context_;
// Temporary storage for building node input lists.
int input_buffer_size_;
@ -127,8 +121,15 @@ class AstGraphBuilder : public AstVisitor {
void set_execution_context(ContextScope* ctx) { execution_context_ = ctx; }
void set_exit_control(Node* exit) { exit_control_ = exit; }
// Create the main graph body by visiting the AST.
void CreateGraphBody();
// Create the node that represents the outer context of the function.
void CreateFunctionContext(bool constant_context);
// Get or create the node that represents the outer function closure.
Node* GetFunctionClosure();
// Node creation helpers.
Node* NewNode(const Operator* op, bool incomplete = false) {
return MakeNode(op, 0, static_cast<Node**>(NULL), incomplete);

View File

@ -291,12 +291,23 @@ class ControlReducerImpl {
}
#if DEBUG
// Verify that no inputs to live nodes are NULL.
for (size_t j = 0; j < nodes.size(); j++) {
Node* node = nodes[j];
for (Node* const input : node->inputs()) {
CHECK(input);
for (Node* node : nodes) {
for (int index = 0; index < node->InputCount(); index++) {
Node* input = node->InputAt(index);
if (input == nullptr) {
std::ostringstream str;
str << "GraphError: node #" << node->id() << ":" << *node->op()
<< "(input @" << index << ") == null";
FATAL(str.str().c_str());
}
if (input->opcode() == IrOpcode::kDead) {
std::ostringstream str;
str << "GraphError: node #" << node->id() << ":" << *node->op()
<< "(input @" << index << ") == dead";
FATAL(str.str().c_str());
}
}
for (Node* const use : node->uses()) {
for (Node* use : node->uses()) {
CHECK(marked.IsReachableFromEnd(use));
}
}

View File

@ -14,11 +14,6 @@ namespace internal {
namespace compiler {
Reduction JSContextSpecializer::Reduce(Node* node) {
if (node == context_) {
Node* constant = jsgraph_->Constant(ctx_);
NodeProperties::ReplaceWithValue(node, constant);
return Replace(constant);
}
if (node->opcode() == IrOpcode::kJSLoadContext) {
return ReduceJSLoadContext(node);
}

View File

@ -17,8 +17,7 @@ namespace compiler {
// some {LoadContext} nodes or strength reducing some {StoreContext} nodes.
class JSContextSpecializer : public Reducer {
public:
JSContextSpecializer(Handle<Context> ctx, JSGraph* jsgraph, Node* context)
: ctx_(ctx), jsgraph_(jsgraph), context_(context) {}
explicit JSContextSpecializer(JSGraph* jsgraph) : jsgraph_(jsgraph) {}
Reduction Reduce(Node* node) OVERRIDE;
@ -27,9 +26,7 @@ class JSContextSpecializer : public Reducer {
Reduction ReduceJSStoreContext(Node* node);
private:
Handle<Context> ctx_;
JSGraph* jsgraph_;
Node* context_;
};
}
}

View File

@ -364,7 +364,7 @@ Reduction JSInliner::TryInlineJSCall(Node* call_node,
jsgraph_->javascript(), jsgraph_->machine());
AstGraphBuilder graph_builder(local_zone_, &info, &jsgraph);
graph_builder.CreateGraph();
graph_builder.CreateGraph(false);
Inlinee::UnifyReturn(&jsgraph);
CopyVisitor visitor(&graph, jsgraph_->graph(), info.zone());

View File

@ -103,6 +103,7 @@ static void PeelOuterLoopsForOsr(Graph* graph, CommonOperatorBuilder* common,
if (backedges == 1) {
// Simple case. Map the incoming edges to the loop to the previous copy.
for (Node* node : loop_tree->HeaderNodes(loop)) {
if (!all.IsLive(node)) continue; // dead phi hanging off loop.
Node* copy = mapping->at(node->id());
Node* backedge = node->InputAt(1);
if (previous) backedge = previous->at(backedge->id());
@ -119,6 +120,7 @@ static void PeelOuterLoopsForOsr(Graph* graph, CommonOperatorBuilder* common,
Node* merge =
graph->NewNode(common->Merge(backedges), backedges, &tmp_inputs[0]);
for (Node* node : loop_tree->HeaderNodes(loop)) {
if (!all.IsLive(node)) continue; // dead phi hanging off loop.
Node* copy = mapping->at(node->id());
if (node == loop_header) {
// The entry to the loop is the merge.
@ -214,7 +216,9 @@ bool OsrHelper::Deconstruct(JSGraph* jsgraph, CommonOperatorBuilder* common,
// Replace the normal entry with {Dead} and the loop entry with {Start}
// and run the control reducer to clean up the graph.
osr_normal_entry->ReplaceUses(dead);
osr_normal_entry->Kill();
osr_loop_entry->ReplaceUses(graph->start());
osr_loop_entry->Kill();
// Normally the control reducer removes loops whose first input is dead,
// but we need to avoid that because the osr_loop is reachable through

View File

@ -74,7 +74,6 @@ class PipelineData {
javascript_(nullptr),
jsgraph_(nullptr),
typer_(nullptr),
context_node_(nullptr),
schedule_(nullptr),
instruction_zone_scope_(zone_pool_),
instruction_zone_(instruction_zone_scope_.zone()),
@ -114,7 +113,6 @@ class PipelineData {
javascript_(nullptr),
jsgraph_(nullptr),
typer_(nullptr),
context_node_(nullptr),
schedule_(schedule),
instruction_zone_scope_(zone_pool_),
instruction_zone_(instruction_zone_scope_.zone()),
@ -141,7 +139,6 @@ class PipelineData {
javascript_(nullptr),
jsgraph_(nullptr),
typer_(nullptr),
context_node_(nullptr),
schedule_(nullptr),
instruction_zone_scope_(zone_pool_),
instruction_zone_(sequence->zone()),
@ -186,12 +183,6 @@ class PipelineData {
loop_assignment_ = loop_assignment;
}
Node* context_node() const { return context_node_; }
void set_context_node(Node* context_node) {
DCHECK(!context_node_);
context_node_ = context_node;
}
Schedule* schedule() const { return schedule_; }
void set_schedule(Schedule* schedule) {
DCHECK(!schedule_);
@ -217,7 +208,6 @@ class PipelineData {
common_ = nullptr;
javascript_ = nullptr;
jsgraph_ = nullptr;
context_node_ = nullptr;
schedule_ = nullptr;
}
@ -272,7 +262,6 @@ class PipelineData {
JSGraph* jsgraph_;
// TODO(dcarney): make this into a ZoneObject.
SmartPointer<Typer> typer_;
Node* context_node_;
Schedule* schedule_;
// All objects in the following group of fields are allocated in
@ -330,9 +319,9 @@ class AstGraphBuilderWithPositions : public AstGraphBuilder {
source_positions_(source_positions),
start_position_(info->shared_info()->start_position()) {}
bool CreateGraph() {
bool CreateGraph(bool constant_context) {
SourcePositionTable::Scope pos_scope(source_positions_, start_position_);
return AstGraphBuilder::CreateGraph();
return AstGraphBuilder::CreateGraph(constant_context);
}
#define DEF_VISIT(type) \
@ -344,8 +333,6 @@ class AstGraphBuilderWithPositions : public AstGraphBuilder {
AST_NODE_LIST(DEF_VISIT)
#undef DEF_VISIT
Node* GetFunctionContext() { return AstGraphBuilder::GetFunctionContext(); }
private:
SourcePositionTable* source_positions_;
SourcePosition start_position_;
@ -433,13 +420,11 @@ struct LoopAssignmentAnalysisPhase {
struct GraphBuilderPhase {
static const char* phase_name() { return "graph builder"; }
void Run(PipelineData* data, Zone* temp_zone) {
void Run(PipelineData* data, Zone* temp_zone, bool constant_context) {
AstGraphBuilderWithPositions graph_builder(
temp_zone, data->info(), data->jsgraph(), data->loop_assignment(),
data->source_positions());
if (graph_builder.CreateGraph()) {
data->set_context_node(graph_builder.GetFunctionContext());
} else {
if (!graph_builder.CreateGraph(constant_context)) {
data->set_compilation_failed();
}
}
@ -452,8 +437,7 @@ struct ContextSpecializerPhase {
void Run(PipelineData* data, Zone* temp_zone) {
SourcePositionTable::Scope pos(data->source_positions(),
SourcePosition::Unknown());
JSContextSpecializer spec(data->info()->context(), data->jsgraph(),
data->context_node());
JSContextSpecializer spec(data->jsgraph());
GraphReducer graph_reducer(data->graph(), temp_zone);
AddReducer(data, &graph_reducer, &spec);
graph_reducer.ReduceGraph();
@ -918,7 +902,7 @@ Handle<Code> Pipeline::GenerateCode() {
Run<LoopAssignmentAnalysisPhase>();
}
Run<GraphBuilderPhase>();
Run<GraphBuilderPhase>(info()->is_context_specializing());
if (data.compilation_failed()) return Handle<Code>::null();
RunPrintAndVerify("Initial untyped", true);

View File

@ -68,45 +68,44 @@ class Verifier::Visitor {
void CheckNotTyped(Node* node) {
if (NodeProperties::IsTyped(node)) {
std::ostringstream str;
str << "TypeError: node #" << node->opcode() << ":"
<< node->op()->mnemonic() << " should never have a type";
V8_Fatal(__FILE__, __LINE__, str.str().c_str());
str << "TypeError: node #" << node->id() << ":" << *node->op()
<< " should never have a type";
FATAL(str.str().c_str());
}
}
void CheckUpperIs(Node* node, Type* type) {
if (typing == TYPED && !bounds(node).upper->Is(type)) {
std::ostringstream str;
str << "TypeError: node #" << node->opcode() << ":"
<< node->op()->mnemonic() << " upper bound ";
str << "TypeError: node #" << node->id() << ":" << *node->op()
<< " upper bound ";
bounds(node).upper->PrintTo(str);
str << " is not ";
type->PrintTo(str);
V8_Fatal(__FILE__, __LINE__, str.str().c_str());
FATAL(str.str().c_str());
}
}
void CheckUpperMaybe(Node* node, Type* type) {
if (typing == TYPED && !bounds(node).upper->Maybe(type)) {
std::ostringstream str;
str << "TypeError: node #" << node->opcode() << ":"
<< node->op()->mnemonic() << " upper bound ";
str << "TypeError: node #" << node->id() << ":" << *node->op()
<< " upper bound ";
bounds(node).upper->PrintTo(str);
str << " must intersect ";
type->PrintTo(str);
V8_Fatal(__FILE__, __LINE__, str.str().c_str());
FATAL(str.str().c_str());
}
}
void CheckValueInputIs(Node* node, int i, Type* type) {
Node* input = ValueInput(node, i);
if (typing == TYPED && !bounds(input).upper->Is(type)) {
std::ostringstream str;
str << "TypeError: node #" << node->opcode() << ":"
<< node->op()->mnemonic() << "(input @" << i << " = "
<< input->opcode() << ":" << input->op()->mnemonic()
<< ") upper bound ";
str << "TypeError: node #" << node->id() << ":" << *node->op()
<< "(input @" << i << " = " << input->opcode() << ":"
<< input->op()->mnemonic() << ") upper bound ";
bounds(input).upper->PrintTo(str);
str << " is not ";
type->PrintTo(str);
V8_Fatal(__FILE__, __LINE__, str.str().c_str());
FATAL(str.str().c_str());
}
}
};

View File

@ -153,6 +153,12 @@ class ControlReducerTester : HandleAndZoneScope {
ReducePhiIterative(expect, phi); // iterative should give the same result.
}
// Checks one-step reduction of a phi.
void ReducePhiNonIterative(Node* expect, Node* phi) {
Node* result = ControlReducer::ReducePhiForTesting(&jsgraph, &common, phi);
CHECK_EQ(expect, result);
}
void ReducePhiIterative(Node* expect, Node* phi) {
p0->ReplaceInput(0, start); // hack: parameters may be trimmed.
Node* ret = graph.NewNode(common.Return(), phi, start, start);
@ -484,10 +490,10 @@ TEST(CReducePhi2_dead) {
for (size_t i = 1; i < kNumLeafs; i++) {
Node* a = R.leaf[i], *b = R.leaf[0];
Node* phi1 = R.Phi(b, a, R.dead);
R.ReducePhi(phi1, phi1);
R.ReducePhiNonIterative(phi1, phi1);
Node* phi2 = R.Phi(a, b, R.dead);
R.ReducePhi(phi2, phi2);
R.ReducePhiNonIterative(phi2, phi2);
}
}

View File

@ -60,7 +60,7 @@ TEST(ReduceJSLoadContext) {
Node* const_context = t.jsgraph()->Constant(native);
Node* deep_const_context = t.jsgraph()->Constant(subcontext2);
Node* param_context = t.NewNode(t.common()->Parameter(0), start);
JSContextSpecializer spec(Handle<Context>(), t.jsgraph(), const_context);
JSContextSpecializer spec(t.jsgraph());
{
// Mutable slot, constant context, depth = 0 => do nothing.
@ -132,7 +132,7 @@ TEST(ReduceJSStoreContext) {
Node* const_context = t.jsgraph()->Constant(native);
Node* deep_const_context = t.jsgraph()->Constant(subcontext2);
Node* param_context = t.NewNode(t.common()->Parameter(0), start);
JSContextSpecializer spec(Handle<Context>(), t.jsgraph(), const_context);
JSContextSpecializer spec(t.jsgraph());
{
// Mutable slot, constant context, depth = 0 => do nothing.
@ -197,7 +197,7 @@ TEST(SpecializeToContext) {
Node* const_context = t.jsgraph()->Constant(native);
Node* param_context = t.NewNode(t.common()->Parameter(0), start);
JSContextSpecializer spec(native, t.jsgraph(), const_context);
JSContextSpecializer spec(t.jsgraph());
{
// Check that specialization replaces values and forwards effects

View File

@ -25,23 +25,3 @@ function foo() {
assertEquals(4950, foo()());
assertEquals(4950, foo()());
assertEquals(4950, foo()());
function bar() {
var result;
{
let sum = 0;
for (let i = 0; i < 90; i++) {
sum += i;
if (i == 45) %OptimizeOsr();
}
result = ret;
function ret() {
return sum;
}
}
return result;
}
assertEquals(4005, bar()());
assertEquals(4005, bar()());
assertEquals(4005, bar()());

View File

@ -6,12 +6,41 @@
"use strict";
function nest(body, name, depth) {
var header = "";
for (var i = 0; i < depth; i++) {
var x = "x" + (i + 1);
header += " for(var " + x + " = 0; " + x + " < 2; " + x + " = " + x + " + 1 | 0) {\n";
body = body + "}"
}
return body.replace(new RegExp("function " + name + "\\(\\) {"),
"function " + name + "_" + x + "() {\n" + header);
}
function test(expected, func, depth) {
assertEquals(expected, func());
assertEquals(expected, func());
assertEquals(expected, func());
var orig = func.toString();
var name = func.name;
for (var depth = 1; depth < 4; depth++) {
var body = nest(orig, name, depth);
func = eval("(" + body + ")");
assertEquals(expected, func());
assertEquals(expected, func());
assertEquals(expected, func());
}
}
function foo() {
var result;
{
let sum = 0;
for (var i = 0; i < 100; i++) {
if (i == 50) %OptimizeOsr();
for (var i = 0; i < 10; i++) {
%OptimizeOsr();
sum += i;
}
result = sum;
@ -19,23 +48,69 @@ function foo() {
return result;
}
assertEquals(4950, foo());
assertEquals(4950, foo());
assertEquals(4950, foo());
test(45, foo);
function bar() {
var result;
let sum = 0;
for (var i = 0; i < 10; i++) {
%OptimizeOsr();
sum += i;
}
return sum;
}
test(45, bar);
function bon() {
{
let sum = 0;
for (let i = 0; i < 90; i++) {
for (var i = 0; i < 10; i++) {
if (i == 5) %OptimizeOsr();
sum += i;
if (i == 45) %OptimizeOsr();
}
result = sum;
return sum;
}
}
test(45, bon);
function row() {
var i = 0;
{
let sum = 0;
while (true) {
if (i == 8) return sum;
%OptimizeOsr();
sum = i;
i = i + 1 | 0;
}
}
return 11;
}
test(7, row);
function nub() {
let i = 0;
while (i < 2) {
%OptimizeOsr();
i++;
}
return i;
}
test(2, nub);
function kub() {
var result = 0;
let i = 0;
while (i < 2) {
let x = i;
%OptimizeOsr();
i++;
result = x;
}
return result;
}
assertEquals(4005, bar());
assertEquals(4005, bar());
assertEquals(4005, bar());
test(1, kub);

View File

@ -0,0 +1,82 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --use-osr --turbo-osr
"use strict";
function test(expected, func) {
assertEquals(expected, func());
assertEquals(expected, func());
assertEquals(expected, func());
}
function bar() {
var result;
{
let sum = 0;
for (let i = 0; i < 90; i++) {
sum += i;
if (i == 45) %OptimizeOsr();
}
result = sum;
}
return result;
}
test(4005, bar);
function baz() {
let sum = 0;
for (let i = 0; i < 2; i++) {
sum = 2;
%OptimizeOsr();
}
return sum;
}
test(2, baz);
function qux() {
var result = 0;
for (let i = 0; i < 2; i++) {
result = i;
%OptimizeOsr();
}
return result;
}
test(1, qux);
function nux() {
var result = 0;
for (let i = 0; i < 2; i++) {
{
let sum = i;
%OptimizeOsr();
result = sum;
}
}
return result;
}
test(1, nux);
function blo() {
var result;
{
let sum = 0;
for (let i = 0; i < 90; i++) {
sum += i;
if (i == 45) %OptimizeOsr();
}
result = ret;
function ret() {
return sum;
}
}
return result;
}
test(4005, blo());

View File

@ -42,7 +42,6 @@ function bar(goal) {
}
}
}
print(count);
return sum;
}

View File

@ -0,0 +1,58 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --use-osr --turbo-osr
"use strict";
function test(expected, func) {
assertEquals(expected, func());
assertEquals(expected, func());
assertEquals(expected, func());
}
function foo() {
var result = 0;
{
let x = 0;
var temp_x = x;
var first = 1;
outer: while (true) {
let x = temp_x;
if (first == 1) first = 0;
else x = x + 1 | 0;
var flag = 1;
for (; flag == 1; (flag = 0, temp_x = x)) {
if (x < 2) {
result = x; %OptimizeOsr();
} else {
break outer;
}
}
if (flag == 1) break;
}
}
return result;
}
test(1, foo);
function smo() {
var result = 0;
{
let x = 11;
outer: while (true) {
let y = x;
for (var i = 0; i < 5; i++) {
%OptimizeOsr();
if (i) break outer;
else result = y;
}
}
}
return result;
}
test(11, smo);