Commit 66d2e1fc authored by leszeks's avatar leszeks Committed by Commit bot

[interpreter] Add a fast path for dynamic local load

Adds a fast path for loading DYNAMIC_LOCAL variables, which are lookup
variables that can be context loaded, without calling the runtime, as
long as there was no context extension by a sloppy eval along their
context chain.

BUG=v8:5263

Review-Url: https://codereview.chromium.org/2343633002
Cr-Commit-Position: refs/heads/master@{#39473}
parent f8ed6fb1
......@@ -888,6 +888,33 @@ void BytecodeGraphBuilder::VisitLdaLookupSlotInsideTypeof() {
BuildLdaLookupSlot(TypeofMode::INSIDE_TYPEOF);
}
void BytecodeGraphBuilder::BuildLdaLookupContextSlot(TypeofMode typeof_mode) {
// TODO(leszeks): Build the fast path here.
// Slow path, do a runtime load lookup.
{
FrameStateBeforeAndAfter states(this);
Node* name =
jsgraph()->Constant(bytecode_iterator().GetConstantForIndexOperand(0));
const Operator* op =
javascript()->CallRuntime(typeof_mode == TypeofMode::NOT_INSIDE_TYPEOF
? Runtime::kLoadLookupSlot
: Runtime::kLoadLookupSlotInsideTypeof);
Node* value = NewNode(op, name);
environment()->BindAccumulator(value, &states);
}
}
void BytecodeGraphBuilder::VisitLdaLookupContextSlot() {
BuildLdaLookupContextSlot(TypeofMode::NOT_INSIDE_TYPEOF);
}
void BytecodeGraphBuilder::VisitLdaLookupContextSlotInsideTypeof() {
BuildLdaLookupContextSlot(TypeofMode::INSIDE_TYPEOF);
}
void BytecodeGraphBuilder::BuildStaLookupSlot(LanguageMode language_mode) {
FrameStateBeforeAndAfter states(this);
Node* value = environment()->LookupAccumulator();
......
......@@ -131,6 +131,7 @@ class BytecodeGraphBuilder {
Node* BuildKeyedLoad();
void BuildKeyedStore(LanguageMode language_mode);
void BuildLdaLookupSlot(TypeofMode typeof_mode);
void BuildLdaLookupContextSlot(TypeofMode typeof_mode);
void BuildStaLookupSlot(LanguageMode language_mode);
void BuildCall(TailCallMode tail_call_mode);
void BuildThrow();
......
......@@ -295,6 +295,18 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadLookupSlot(
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::LoadLookupContextSlot(
const Handle<String> name, TypeofMode typeof_mode, int slot_index,
int depth) {
Bytecode bytecode = (typeof_mode == INSIDE_TYPEOF)
? Bytecode::kLdaLookupContextSlotInsideTypeof
: Bytecode::kLdaLookupContextSlot;
size_t name_index = GetConstantPoolEntry(name);
Output(bytecode, UnsignedOperand(name_index), UnsignedOperand(slot_index),
UnsignedOperand(depth));
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::StoreLookupSlot(
const Handle<String> name, LanguageMode language_mode) {
Bytecode bytecode = BytecodeForStoreLookupSlot(language_mode);
......
......@@ -131,6 +131,13 @@ class BytecodeArrayBuilder final : public ZoneObject {
BytecodeArrayBuilder& LoadLookupSlot(const Handle<String> name,
TypeofMode typeof_mode);
// Lookup the variable with |name|, which is known to be at |slot_index| at
// |depth| in the context chain if not shadowed by a context extension
// somewhere in that context chain.
BytecodeArrayBuilder& LoadLookupContextSlot(const Handle<String> name,
TypeofMode typeof_mode,
int slot_index, int depth);
// Store value in the accumulator into the variable with |name|.
BytecodeArrayBuilder& StoreLookupSlot(const Handle<String> name,
LanguageMode language_mode);
......
......@@ -1967,7 +1967,19 @@ void BytecodeGenerator::VisitVariableLoad(Variable* variable,
break;
}
case VariableLocation::LOOKUP: {
switch (variable->mode()) {
case DYNAMIC_LOCAL: {
Variable* local_variable = variable->local_if_not_shadowed();
int depth =
execution_context()->ContextChainDepth(local_variable->scope());
builder()->LoadLookupContextSlot(variable->name(), typeof_mode,
local_variable->index(), depth);
BuildHoleCheckForVariableLoad(variable);
break;
}
default:
builder()->LoadLookupSlot(variable->name(), typeof_mode);
}
break;
}
case VariableLocation::MODULE: {
......
......@@ -115,7 +115,11 @@ namespace interpreter {
\
/* Load-Store lookup slots */ \
V(LdaLookupSlot, AccumulatorUse::kWrite, OperandType::kIdx) \
V(LdaLookupContextSlot, AccumulatorUse::kWrite, OperandType::kIdx, \
OperandType::kIdx, OperandType::kUImm) \
V(LdaLookupSlotInsideTypeof, AccumulatorUse::kWrite, OperandType::kIdx) \
V(LdaLookupContextSlotInsideTypeof, AccumulatorUse::kWrite, \
OperandType::kIdx, OperandType::kIdx, OperandType::kUImm) \
V(StaLookupSlotSloppy, AccumulatorUse::kReadWrite, OperandType::kIdx) \
V(StaLookupSlotStrict, AccumulatorUse::kReadWrite, OperandType::kIdx) \
\
......
......@@ -114,6 +114,41 @@ Node* InterpreterAssembler::GetContextAtDepth(Node* context, Node* depth) {
return cur_context.value();
}
void InterpreterAssembler::GotoIfHasContextExtensionUpToDepth(Node* context,
Node* depth,
Label* target) {
Variable cur_context(this, MachineRepresentation::kTaggedPointer);
cur_context.Bind(context);
Variable cur_depth(this, MachineRepresentation::kWord32);
cur_depth.Bind(depth);
Variable* context_search_loop_variables[2] = {&cur_depth, &cur_context};
Label context_search(this, 2, context_search_loop_variables);
// Loop until the depth is 0.
Goto(&context_search);
Bind(&context_search);
{
// TODO(leszeks): We only need to do this check if the context had a sloppy
// eval, we could pass in a context chain bitmask to figure out which
// contexts actually need to be checked.
Node* extension_slot =
LoadContextSlot(cur_context.value(), Context::EXTENSION_INDEX);
// Jump to the target if the extension slot is not a hole.
GotoIf(WordNotEqual(extension_slot, TheHoleConstant()), target);
cur_depth.Bind(Int32Sub(cur_depth.value(), Int32Constant(1)));
cur_context.Bind(
LoadContextSlot(cur_context.value(), Context::PREVIOUS_INDEX));
GotoIf(Word32NotEqual(cur_depth.value(), Int32Constant(0)),
&context_search);
}
}
Node* InterpreterAssembler::BytecodeOffset() {
return bytecode_offset_.value();
}
......
......@@ -56,10 +56,15 @@ class InterpreterAssembler : public CodeStubAssembler {
compiler::Node* GetContext();
void SetContext(compiler::Node* value);
// Context at |depth| in the context chain starting at |context|
// Context at |depth| in the context chain starting at |context|.
compiler::Node* GetContextAtDepth(compiler::Node* context,
compiler::Node* depth);
// Goto the given |target| if the context chain starting at |context| has any
// extensions up to the given |depth|.
void GotoIfHasContextExtensionUpToDepth(compiler::Node* context,
compiler::Node* depth, Label* target);
// Number of registers.
compiler::Node* RegisterCount();
......
......@@ -587,6 +587,53 @@ void Interpreter::DoLdaLookupSlotInsideTypeof(InterpreterAssembler* assembler) {
DoLdaLookupSlot(Runtime::kLoadLookupSlotInsideTypeof, assembler);
}
void Interpreter::DoLdaLookupContextSlot(Runtime::FunctionId function_id,
InterpreterAssembler* assembler) {
Node* context = __ GetContext();
Node* name_index = __ BytecodeOperandIdx(0);
Node* slot_index = __ BytecodeOperandIdx(1);
Node* depth = __ BytecodeOperandUImm(2);
Label slowpath(assembler, Label::kDeferred);
// Check for context extensions to allow the fast path.
__ GotoIfHasContextExtensionUpToDepth(context, depth, &slowpath);
// Fast path does a normal load context.
{
Node* slot_context = __ GetContextAtDepth(context, depth);
Node* result = __ LoadContextSlot(slot_context, slot_index);
__ SetAccumulator(result);
__ Dispatch();
}
// Slow path when we have to call out to the runtime.
__ Bind(&slowpath);
{
Node* name = __ LoadConstantPoolEntry(name_index);
Node* result = __ CallRuntime(function_id, context, name);
__ SetAccumulator(result);
__ Dispatch();
}
}
// LdaLookupSlot <name_index>
//
// Lookup the object with the name in constant pool entry |name_index|
// dynamically.
void Interpreter::DoLdaLookupContextSlot(InterpreterAssembler* assembler) {
DoLdaLookupContextSlot(Runtime::kLoadLookupSlot, assembler);
}
// LdaLookupSlotInsideTypeof <name_index>
//
// Lookup the object with the name in constant pool entry |name_index|
// dynamically without causing a NoReferenceError.
void Interpreter::DoLdaLookupContextSlotInsideTypeof(
InterpreterAssembler* assembler) {
DoLdaLookupContextSlot(Runtime::kLoadLookupSlotInsideTypeof, assembler);
}
void Interpreter::DoStaLookupSlot(LanguageMode language_mode,
InterpreterAssembler* assembler) {
Node* value = __ GetAccumulator();
......
......@@ -132,6 +132,11 @@ class Interpreter {
void DoLdaLookupSlot(Runtime::FunctionId function_id,
InterpreterAssembler* assembler);
// Generates code to perform a lookup slot load via |function_id| that can
// fast path to a context slot load.
void DoLdaLookupContextSlot(Runtime::FunctionId function_id,
InterpreterAssembler* assembler);
// Generates code to perform a lookup slot store depending on |language_mode|.
void DoStaLookupSlot(LanguageMode language_mode,
InterpreterAssembler* assembler);
......
......@@ -1067,6 +1067,55 @@ TEST(BytecodeGraphBuilderLookupSlot) {
}
}
TEST(BytecodeGraphBuilderLookupContextSlot) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
Factory* factory = isolate->factory();
// Testing with eval called in the current context.
const char* inner_eval_prologue = "var x = 0; function inner() {";
const char* inner_eval_epilogue = "}; return inner();";
ExpectedSnippet<0> inner_eval_snippets[] = {
{"eval(''); return x;", {factory->NewNumber(0)}},
{"eval('var x = 1'); return x;", {factory->NewNumber(1)}},
{"'use strict'; eval('var x = 1'); return x;", {factory->NewNumber(0)}}};
for (size_t i = 0; i < arraysize(inner_eval_snippets); i++) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s(p1) { %s %s %s } ; %s() ;", kFunctionName,
inner_eval_prologue, inner_eval_snippets[i].code_snippet,
inner_eval_epilogue, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*inner_eval_snippets[i].return_value()));
}
// Testing with eval called in a parent context.
const char* outer_eval_prologue = "";
const char* outer_eval_epilogue =
"function inner() { return x; }; return inner();";
ExpectedSnippet<0> outer_eval_snippets[] = {
{"var x = 0; eval('');", {factory->NewNumber(0)}},
{"var x = 0; eval('var x = 1');", {factory->NewNumber(1)}},
{"'use strict'; var x = 0; eval('var x = 1');", {factory->NewNumber(0)}}};
for (size_t i = 0; i < arraysize(outer_eval_snippets); i++) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s() { %s %s %s } ; %s() ;", kFunctionName,
outer_eval_prologue, outer_eval_snippets[i].code_snippet,
outer_eval_epilogue, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*outer_eval_snippets[i].return_value()));
}
}
TEST(BytecodeGraphBuilderLookupSlotWide) {
HandleAndZoneScope scope;
......
......@@ -5,6 +5,7 @@
---
execute: yes
wrap: yes
test function name: f
---
snippet: "
......@@ -22,26 +23,26 @@ bytecodes: [
B(StaContextSlot), R(context), U8(6), U8(0),
B(Ldar), R(new_target),
B(StaContextSlot), R(context), U8(5), U8(0),
/* 30 E> */ B(StackCheck),
/* 34 S> */ B(LdaConstant), U8(0),
/* 10 E> */ B(StackCheck),
/* 14 S> */ B(LdaConstant), U8(0),
B(Star), R(3),
B(CallRuntimeForPair), U16(Runtime::kLoadLookupSlotForCall), R(3), U8(1), R(1),
B(LdaConstant), U8(1),
B(Star), R(3),
B(LdaZero),
B(Star), R(7),
B(LdaSmi), U8(30),
B(LdaSmi), U8(10),
B(Star), R(8),
B(LdaSmi), U8(34),
B(LdaSmi), U8(14),
B(Star), R(9),
B(Mov), R(1), R(4),
B(Mov), R(3), R(5),
B(Mov), R(closure), R(6),
B(CallRuntime), U16(Runtime::kResolvePossiblyDirectEval), R(4), U8(6),
B(Star), R(1),
/* 34 E> */ B(Call), R(1), R(2), U8(2), U8(0),
/* 55 S> */ B(LdaLookupSlot), U8(2),
/* 65 S> */ B(Return),
/* 14 E> */ B(Call), R(1), R(2), U8(2), U8(0),
/* 35 S> */ B(LdaLookupSlot), U8(2),
/* 45 S> */ B(Return),
]
constant pool: [
ONE_BYTE_INTERNALIZED_STRING_TYPE ["eval"],
......@@ -67,27 +68,27 @@ bytecodes: [
B(StaContextSlot), R(context), U8(6), U8(0),
B(Ldar), R(new_target),
B(StaContextSlot), R(context), U8(5), U8(0),
/* 30 E> */ B(StackCheck),
/* 34 S> */ B(LdaConstant), U8(0),
/* 10 E> */ B(StackCheck),
/* 14 S> */ B(LdaConstant), U8(0),
B(Star), R(3),
B(CallRuntimeForPair), U16(Runtime::kLoadLookupSlotForCall), R(3), U8(1), R(1),
B(LdaConstant), U8(1),
B(Star), R(3),
B(LdaZero),
B(Star), R(7),
B(LdaSmi), U8(30),
B(LdaSmi), U8(10),
B(Star), R(8),
B(LdaSmi), U8(34),
B(LdaSmi), U8(14),
B(Star), R(9),
B(Mov), R(1), R(4),
B(Mov), R(3), R(5),
B(Mov), R(closure), R(6),
B(CallRuntime), U16(Runtime::kResolvePossiblyDirectEval), R(4), U8(6),
B(Star), R(1),
/* 34 E> */ B(Call), R(1), R(2), U8(2), U8(0),
/* 55 S> */ B(LdaLookupSlotInsideTypeof), U8(2),
/* 14 E> */ B(Call), R(1), R(2), U8(2), U8(0),
/* 35 S> */ B(LdaLookupSlotInsideTypeof), U8(2),
B(TypeOf),
/* 72 S> */ B(Return),
/* 52 S> */ B(Return),
]
constant pool: [
ONE_BYTE_INTERNALIZED_STRING_TYPE ["eval"],
......@@ -113,27 +114,27 @@ bytecodes: [
B(StaContextSlot), R(context), U8(6), U8(0),
B(Ldar), R(new_target),
B(StaContextSlot), R(context), U8(5), U8(0),
/* 30 E> */ B(StackCheck),
/* 34 S> */ B(LdaSmi), U8(20),
/* 36 E> */ B(StaLookupSlotSloppy), U8(0),
/* 42 S> */ B(LdaConstant), U8(1),
/* 10 E> */ B(StackCheck),
/* 14 S> */ B(LdaSmi), U8(20),
/* 16 E> */ B(StaLookupSlotSloppy), U8(0),
/* 22 S> */ B(LdaConstant), U8(1),
B(Star), R(3),
B(CallRuntimeForPair), U16(Runtime::kLoadLookupSlotForCall), R(3), U8(1), R(1),
B(LdaConstant), U8(2),
B(Star), R(3),
B(LdaZero),
B(Star), R(7),
B(LdaSmi), U8(30),
B(LdaSmi), U8(10),
B(Star), R(8),
B(LdaSmi), U8(49),
B(LdaSmi), U8(29),
B(Star), R(9),
B(Mov), R(1), R(4),
B(Mov), R(3), R(5),
B(Mov), R(closure), R(6),
B(CallRuntime), U16(Runtime::kResolvePossiblyDirectEval), R(4), U8(6),
B(Star), R(1),
/* 49 E> */ B(Call), R(1), R(2), U8(2), U8(0),
/* 59 S> */ B(Return),
/* 29 E> */ B(Call), R(1), R(2), U8(2), U8(0),
/* 39 S> */ B(Return),
]
constant pool: [
ONE_BYTE_INTERNALIZED_STRING_TYPE ["x"],
......@@ -143,3 +144,53 @@ constant pool: [
handlers: [
]
---
snippet: "
var x = 20;
f = function(){
eval('var x = 10');
return x;
}
f();
"
frame size: 10
parameter count: 1
bytecode array length: 73
bytecodes: [
B(CreateFunctionContext), U8(3),
B(PushContext), R(0),
B(Ldar), R(this),
B(StaContextSlot), R(context), U8(4), U8(0),
B(CreateMappedArguments),
B(StaContextSlot), R(context), U8(6), U8(0),
B(Ldar), R(new_target),
B(StaContextSlot), R(context), U8(5), U8(0),
/* 38 E> */ B(StackCheck),
/* 44 S> */ B(LdaConstant), U8(0),
B(Star), R(3),
B(CallRuntimeForPair), U16(Runtime::kLoadLookupSlotForCall), R(3), U8(1), R(1),
B(LdaConstant), U8(1),
B(Star), R(3),
B(LdaZero),
B(Star), R(7),
B(LdaSmi), U8(38),
B(Star), R(8),
B(LdaSmi), U8(44),
B(Star), R(9),
B(Mov), R(1), R(4),
B(Mov), R(3), R(5),
B(Mov), R(closure), R(6),
B(CallRuntime), U16(Runtime::kResolvePossiblyDirectEval), R(4), U8(6),
B(Star), R(1),
/* 44 E> */ B(Call), R(1), R(2), U8(2), U8(0),
/* 66 S> */ B(LdaLookupContextSlot), U8(2), U8(6), U8(1),
/* 76 S> */ B(Return),
]
constant pool: [
ONE_BYTE_INTERNALIZED_STRING_TYPE ["eval"],
ONE_BYTE_INTERNALIZED_STRING_TYPE ["var x = 10"],
ONE_BYTE_INTERNALIZED_STRING_TYPE ["x"],
]
handlers: [
]
......@@ -1799,14 +1799,24 @@ TEST(Eval) {
TEST(LookupSlot) {
InitializedIgnitionHandleScope scope;
BytecodeExpectationsPrinter printer(CcTest::isolate());
printer.set_test_function_name("f");
// clang-format off
const char* snippets[] = {
"eval('var x = 10;'); return x;\n",
"eval('var x = 10;'); return typeof x;\n",
"x = 20; return eval('');\n",
"var x = 20;\n"
"f = function(){\n"
" eval('var x = 10');\n"
" return x;\n"
"}\n"
"f();\n"
};
// clang-format on
CHECK(CompareTexts(BuildActual(printer, snippets),
LoadGolden("LookupSlot.golden")));
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <tuple>
#include "src/v8.h"
#include "src/execution.h"
......@@ -3862,6 +3864,47 @@ TEST(InterpreterLookupSlot) {
}
}
TEST(InterpreterLookupContextSlot) {
HandleAndZoneScope handles;
Isolate* isolate = handles.main_isolate();
const char* inner_function_prologue = "function inner() {";
const char* inner_function_epilogue = "};";
const char* outer_function_epilogue = "return inner();";
std::tuple<const char*, const char*, Handle<Object>> lookup_slot[] = {
// Eval in inner context.
std::make_tuple("var x = 0;", "eval(''); return x;",
handle(Smi::FromInt(0), isolate)),
std::make_tuple("var x = 0;", "eval('var x = 1'); return x;",
handle(Smi::FromInt(1), isolate)),
std::make_tuple("var x = 0;",
"'use strict'; eval('var x = 1'); return x;",
handle(Smi::FromInt(0), isolate)),
// Eval in outer context.
std::make_tuple("var x = 0; eval('');", "return x;",
handle(Smi::FromInt(0), isolate)),
std::make_tuple("var x = 0; eval('var x = 1');", "return x;",
handle(Smi::FromInt(1), isolate)),
std::make_tuple("'use strict'; var x = 0; eval('var x = 1');",
"return x;", handle(Smi::FromInt(0), isolate)),
};
for (size_t i = 0; i < arraysize(lookup_slot); i++) {
std::string body = std::string(std::get<0>(lookup_slot[i])) +
std::string(inner_function_prologue) +
std::string(std::get<1>(lookup_slot[i])) +
std::string(inner_function_epilogue) +
std::string(outer_function_epilogue);
std::string script = InterpreterTester::SourceForBody(body.c_str());
InterpreterTester tester(isolate, script.c_str());
auto callable = tester.GetCallable<>();
Handle<i::Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*std::get<2>(lookup_slot[i])));
}
}
TEST(InterpreterCallLookupSlot) {
HandleAndZoneScope handles;
......
......@@ -100,6 +100,10 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.StoreLookupSlot(name, LanguageMode::SLOPPY)
.StoreLookupSlot(name, LanguageMode::STRICT);
// Emit load / store lookup slots with context fast paths.
builder.LoadLookupContextSlot(name, TypeofMode::NOT_INSIDE_TYPEOF, 1, 0)
.LoadLookupContextSlot(name, TypeofMode::INSIDE_TYPEOF, 1, 0);
// Emit closure operations.
builder.CreateClosure(0, NOT_TENURED);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment