[Interpreter] Add support for context slot loads / stores to BytecodeGraphBuilder.
Adds implementation and tests for LdaContextSlot, StaeContextSlot, PushContext, and PopContext to bytecode graph builder BUG=v8:4280 LOG=N Review URL: https://codereview.chromium.org/1489863002 Cr-Commit-Position: refs/heads/master@{#32474}
This commit is contained in:
parent
df36d046a4
commit
128e75cbd9
@ -7,6 +7,7 @@
|
||||
#include "src/compiler/linkage.h"
|
||||
#include "src/compiler/operator-properties.h"
|
||||
#include "src/interpreter/bytecode-array-iterator.h"
|
||||
#include "src/interpreter/bytecodes.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -73,8 +74,16 @@ void BytecodeGraphBuilder::Environment::BindRegister(
|
||||
|
||||
Node* BytecodeGraphBuilder::Environment::LookupRegister(
|
||||
interpreter::Register the_register) const {
|
||||
int values_index = RegisterToValuesIndex(the_register);
|
||||
return values()->at(values_index);
|
||||
if (the_register.is_function_context()) {
|
||||
return builder()->GetFunctionContext();
|
||||
} else if (the_register.is_function_closure()) {
|
||||
return builder()->GetFunctionClosure();
|
||||
} else if (the_register.is_new_target()) {
|
||||
return builder()->GetNewTarget();
|
||||
} else {
|
||||
int values_index = RegisterToValuesIndex(the_register);
|
||||
return values()->at(values_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -111,6 +120,18 @@ BytecodeGraphBuilder::BytecodeGraphBuilder(Zone* local_zone,
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::GetNewTarget() {
|
||||
if (!new_target_.is_set()) {
|
||||
int params = bytecode_array()->parameter_count();
|
||||
int index = Linkage::GetJSCallNewTargetParamIndex(params);
|
||||
const Operator* op = common()->Parameter(index, "%new.target");
|
||||
Node* node = NewNode(op, graph()->start());
|
||||
new_target_.set(node);
|
||||
}
|
||||
return new_target_.get();
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::GetFunctionContext() {
|
||||
if (!function_context_.is_set()) {
|
||||
int params = bytecode_array()->parameter_count();
|
||||
@ -204,13 +225,7 @@ bool BytecodeGraphBuilder::CreateGraph(bool stack_check) {
|
||||
GetFunctionContext());
|
||||
set_environment(&env);
|
||||
|
||||
// Build function context only if there are context allocated variables.
|
||||
if (info()->num_heap_slots() > 0) {
|
||||
UNIMPLEMENTED(); // TODO(oth): Write ast-graph-builder equivalent.
|
||||
} else {
|
||||
// Simply use the outer function context in building the graph.
|
||||
CreateGraphBody(stack_check);
|
||||
}
|
||||
CreateGraphBody(stack_check);
|
||||
|
||||
// Finish the basic structure of the graph.
|
||||
DCHECK_NE(0u, exit_controls_.size());
|
||||
@ -443,13 +458,32 @@ void BytecodeGraphBuilder::VisitStaGlobalStrictWide(
|
||||
|
||||
void BytecodeGraphBuilder::VisitLdaContextSlot(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
// TODO(mythria): LoadContextSlots are unrolled by the required depth when
|
||||
// generating bytecode. Hence the value of depth is always 0. Update this
|
||||
// code, when the implementation changes.
|
||||
// TODO(mythria): immutable flag is also set to false. This information is not
|
||||
// available in bytecode array. update this code when the implementation
|
||||
// changes.
|
||||
const Operator* op =
|
||||
javascript()->LoadContext(0, iterator.GetIndexOperand(1), false);
|
||||
Node* context = environment()->LookupRegister(iterator.GetRegisterOperand(0));
|
||||
Node* node = NewNode(op, context);
|
||||
environment()->BindAccumulator(node);
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitStaContextSlot(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
// TODO(mythria): LoadContextSlots are unrolled by the required depth when
|
||||
// generating bytecode. Hence the value of depth is always 0. Update this
|
||||
// code, when the implementation changes.
|
||||
const Operator* op =
|
||||
javascript()->StoreContext(0, iterator.GetIndexOperand(1));
|
||||
Node* context = environment()->LookupRegister(iterator.GetRegisterOperand(0));
|
||||
Node* value = environment()->LookupAccumulator();
|
||||
Node* node = NewNode(op, context, value);
|
||||
CHECK(node != nullptr);
|
||||
environment()->BindAccumulator(value);
|
||||
}
|
||||
|
||||
|
||||
@ -624,13 +658,16 @@ void BytecodeGraphBuilder::VisitKeyedStoreICStrictWide(
|
||||
|
||||
void BytecodeGraphBuilder::VisitPushContext(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
Node* context = environment()->LookupAccumulator();
|
||||
environment()->BindRegister(iterator.GetRegisterOperand(0), context);
|
||||
environment()->SetContext(context);
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitPopContext(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
Node* context = environment()->LookupRegister(iterator.GetRegisterOperand(0));
|
||||
environment()->SetContext(context);
|
||||
}
|
||||
|
||||
|
||||
|
@ -40,6 +40,9 @@ class BytecodeGraphBuilder {
|
||||
// Get or create the node that represents the outer function context.
|
||||
Node* GetFunctionContext();
|
||||
|
||||
// Get or create the node that represents the incoming new target value.
|
||||
Node* GetNewTarget();
|
||||
|
||||
// Builder for accessing a (potentially immutable) object field.
|
||||
Node* BuildLoadObjectField(Node* object, int offset);
|
||||
Node* BuildLoadImmutableObjectField(Node* object, int offset);
|
||||
@ -158,6 +161,7 @@ class BytecodeGraphBuilder {
|
||||
// Nodes representing values in the activation record.
|
||||
SetOncePointer<Node> function_context_;
|
||||
SetOncePointer<Node> function_closure_;
|
||||
SetOncePointer<Node> new_target_;
|
||||
|
||||
// Optimization to cache loaded feedback vector.
|
||||
SetOncePointer<Node> feedback_vector_;
|
||||
@ -199,6 +203,7 @@ class BytecodeGraphBuilder::Environment : public ZoneObject {
|
||||
}
|
||||
|
||||
Node* Context() const { return context_; }
|
||||
void SetContext(Node* new_context) { context_ = new_context; }
|
||||
|
||||
private:
|
||||
int RegisterToValuesIndex(interpreter::Register the_register) const;
|
||||
|
@ -1328,6 +1328,8 @@ void BytecodeGenerator::VisitVariableLoad(Variable* variable,
|
||||
// Walk the context chain to find the context at the given depth.
|
||||
// TODO(rmcilroy): Perform this work in a bytecode handler once we have
|
||||
// a generic mechanism for performing jumps in interpreter.cc.
|
||||
// TODO(mythria): Also update bytecode graph builder with correct depth
|
||||
// when this changes.
|
||||
builder()
|
||||
->LoadAccumulatorWithRegister(execution_context()->reg())
|
||||
.StoreAccumulatorInRegister(context_reg);
|
||||
@ -1404,6 +1406,8 @@ void BytecodeGenerator::VisitVariableAssignment(Variable* variable,
|
||||
// Walk the context chain to find the context at the given depth.
|
||||
// TODO(rmcilroy): Perform this work in a bytecode handler once we have
|
||||
// a generic mechanism for performing jumps in interpreter.cc.
|
||||
// TODO(mythria): Also update bytecode graph builder with correct depth
|
||||
// when this changes.
|
||||
builder()
|
||||
->StoreAccumulatorInRegister(value_temp)
|
||||
.LoadAccumulatorWithRegister(execution_context()->reg())
|
||||
|
@ -67,7 +67,8 @@ class BytecodeGraphCallable {
|
||||
|
||||
class BytecodeGraphTester {
|
||||
public:
|
||||
BytecodeGraphTester(Isolate* isolate, Zone* zone, const char* script)
|
||||
BytecodeGraphTester(Isolate* isolate, Zone* zone, const char* script,
|
||||
const char* filter = kFunctionName)
|
||||
: isolate_(isolate), zone_(zone), script_(script) {
|
||||
i::FLAG_ignition = true;
|
||||
i::FLAG_always_opt = false;
|
||||
@ -75,7 +76,7 @@ class BytecodeGraphTester {
|
||||
// Set ignition filter flag via SetFlagsFromString to avoid double-free
|
||||
// (or potential leak with StrDup() based on ownership confusion).
|
||||
ScopedVector<char> ignition_filter(64);
|
||||
SNPrintF(ignition_filter, "--ignition-filter=%s", kFunctionName);
|
||||
SNPrintF(ignition_filter, "--ignition-filter=%s", filter);
|
||||
FlagList::SetFlagsFromString(ignition_filter.start(),
|
||||
ignition_filter.length());
|
||||
// Ensure handler table is generated.
|
||||
@ -84,8 +85,9 @@ class BytecodeGraphTester {
|
||||
virtual ~BytecodeGraphTester() {}
|
||||
|
||||
template <class... A>
|
||||
BytecodeGraphCallable<A...> GetCallable() {
|
||||
return BytecodeGraphCallable<A...>(isolate_, GetFunction());
|
||||
BytecodeGraphCallable<A...> GetCallable(
|
||||
const char* functionName = kFunctionName) {
|
||||
return BytecodeGraphCallable<A...>(isolate_, GetFunction(functionName));
|
||||
}
|
||||
|
||||
Local<Message> CheckThrowsReturnMessage() {
|
||||
@ -109,11 +111,11 @@ class BytecodeGraphTester {
|
||||
Zone* zone_;
|
||||
const char* script_;
|
||||
|
||||
Handle<JSFunction> GetFunction() {
|
||||
Handle<JSFunction> GetFunction(const char* functionName) {
|
||||
CompileRun(script_);
|
||||
Local<Function> api_function = Local<Function>::Cast(
|
||||
CcTest::global()
|
||||
->Get(CcTest::isolate()->GetCurrentContext(), v8_str(kFunctionName))
|
||||
->Get(CcTest::isolate()->GetCurrentContext(), v8_str(functionName))
|
||||
.ToLocalChecked());
|
||||
Handle<JSFunction> function =
|
||||
Handle<JSFunction>::cast(v8::Utils::OpenHandle(*api_function));
|
||||
@ -1002,6 +1004,133 @@ TEST(BytecodeGraphBuilderThrow) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderContext) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
Zone* zone = scope.main_zone();
|
||||
Factory* factory = isolate->factory();
|
||||
|
||||
ExpectedSnippet<0> snippets[] = {
|
||||
{"var x = 'outer';"
|
||||
"function f() {"
|
||||
" 'use strict';"
|
||||
" {"
|
||||
" let x = 'inner';"
|
||||
" (function() {x});"
|
||||
" }"
|
||||
"return(x);"
|
||||
"}"
|
||||
"f();",
|
||||
{factory->NewStringFromStaticChars("outer")}},
|
||||
{"var x = 'outer';"
|
||||
"function f() {"
|
||||
" 'use strict';"
|
||||
" {"
|
||||
" let x = 'inner ';"
|
||||
" var innerFunc = function() {return x};"
|
||||
" }"
|
||||
"return(innerFunc() + x);"
|
||||
"}"
|
||||
"f();",
|
||||
{factory->NewStringFromStaticChars("inner outer")}},
|
||||
{"var x = 'outer';"
|
||||
"function f() {"
|
||||
" 'use strict';"
|
||||
" {"
|
||||
" let x = 'inner ';"
|
||||
" var innerFunc = function() {return x;};"
|
||||
" {"
|
||||
" let x = 'innermost ';"
|
||||
" var innerMostFunc = function() {return x + innerFunc();};"
|
||||
" }"
|
||||
" x = 'inner_changed ';"
|
||||
" }"
|
||||
" return(innerMostFunc() + x);"
|
||||
"}"
|
||||
"f();",
|
||||
{factory->NewStringFromStaticChars("innermost inner_changed outer")}},
|
||||
};
|
||||
|
||||
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
|
||||
for (size_t i = 0; i < num_snippets; i++) {
|
||||
ScopedVector<char> script(1024);
|
||||
SNPrintF(script, "%s", snippets[i].code_snippet);
|
||||
|
||||
BytecodeGraphTester tester(isolate, zone, script.start(), "f");
|
||||
auto callable = tester.GetCallable<>("f");
|
||||
Handle<Object> return_value = callable().ToHandleChecked();
|
||||
CHECK(return_value->SameValue(*snippets[i].return_value()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderLoadContext) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
Zone* zone = scope.main_zone();
|
||||
Factory* factory = isolate->factory();
|
||||
|
||||
ExpectedSnippet<1> snippets[] = {
|
||||
{"function Outer() {"
|
||||
" var outerVar = 2;"
|
||||
" function Inner(innerArg) {"
|
||||
" this.innerFunc = function () {"
|
||||
" return outerVar * innerArg;"
|
||||
" };"
|
||||
" };"
|
||||
" this.getInnerFunc = function GetInner() {"
|
||||
" return new Inner(3).innerFunc;"
|
||||
" }"
|
||||
"}"
|
||||
"var f = new Outer().getInnerFunc();"
|
||||
"f();",
|
||||
{factory->NewNumberFromInt(6), factory->undefined_value()}},
|
||||
{"function Outer() {"
|
||||
" var outerVar = 2;"
|
||||
" function Inner(innerArg) {"
|
||||
" this.innerFunc = function () {"
|
||||
" outerVar = innerArg; return outerVar;"
|
||||
" };"
|
||||
" };"
|
||||
" this.getInnerFunc = function GetInner() {"
|
||||
" return new Inner(10).innerFunc;"
|
||||
" }"
|
||||
"}"
|
||||
"var f = new Outer().getInnerFunc();"
|
||||
"f();",
|
||||
{factory->NewNumberFromInt(10), factory->undefined_value()}},
|
||||
{"function testOuter(outerArg) {"
|
||||
" this.testinnerFunc = function testInner(innerArg) {"
|
||||
" return innerArg + outerArg;"
|
||||
" }"
|
||||
"}"
|
||||
"var f = new testOuter(10).testinnerFunc;"
|
||||
"f(0);",
|
||||
{factory->NewNumberFromInt(14), factory->NewNumberFromInt(4)}},
|
||||
{"function testOuter(outerArg) {"
|
||||
" var outerVar = outerArg * 2;"
|
||||
" this.testinnerFunc = function testInner(innerArg) {"
|
||||
" outerVar = outerVar + innerArg; return outerVar;"
|
||||
" }"
|
||||
"}"
|
||||
"var f = new testOuter(10).testinnerFunc;"
|
||||
"f(0);",
|
||||
{factory->NewNumberFromInt(24), factory->NewNumberFromInt(4)}}};
|
||||
|
||||
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
|
||||
for (size_t i = 0; i < num_snippets; i++) {
|
||||
ScopedVector<char> script(1024);
|
||||
SNPrintF(script, "%s", snippets[i].code_snippet);
|
||||
|
||||
BytecodeGraphTester tester(isolate, zone, script.start(), "*");
|
||||
auto callable = tester.GetCallable<Handle<Object>>("f");
|
||||
Handle<Object> return_value =
|
||||
callable(snippets[i].parameter(0)).ToHandleChecked();
|
||||
CHECK(return_value->SameValue(*snippets[i].return_value()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user