Commit 1191e6f6 authored by Marja Hölttä's avatar Marja Hölttä Committed by Commit Bot

[parser] Skipping inner funcs: store and use the inner function data.

The data needed to be modified a bit to actually allow skipping over functions
based on it. In particular, we need to allow skipping over an unknown inner
scope structure (in the previous stage, we just had tests comparing the data
against some baseline truth, so it wasn't needed).

also removing the current "skip functions based on preparse data" logic,
since preparser data is not used any more. At a later stage, I'll consider
plugging the preparser-scope-analysis-data into that pipeline (so I don't want
to remove the full code yet).

Integration to the various forms of compilation is still incomplete; this CL
integrates just enough to get the minimal example to pass:

(function foo() {
  function preparsed() {
    var var1 = 10;
    function skip_me() {
      print(var1);
    }
    return skip_me;
  }
  return preparsed;
})()()();

BUG=v8:5516

Change-Id: I0d24b4c3b338f7e6b6c3bf7cf2c1ceb29608e2f2
Reviewed-on: https://chromium-review.googlesource.com/446336
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarDaniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#43908}
parent 3700a01c
......@@ -262,6 +262,9 @@ Scope::Scope(Zone* zone, ScopeType scope_type, Handle<ScopeInfo> scope_info)
set_language_mode(scope_info->language_mode());
num_heap_slots_ = scope_info->ContextLength();
DCHECK_LE(Context::MIN_CONTEXT_SLOTS, num_heap_slots_);
// We don't really need to use the preparsed scope data; this is just to
// shorten the recursion in SetMustUsePreParsedScopeData.
must_use_preparsed_scope_data_ = true;
}
DeclarationScope::DeclarationScope(Zone* zone, ScopeType scope_type,
......@@ -309,6 +312,7 @@ void DeclarationScope::SetDefaults() {
rare_data_ = nullptr;
should_eager_compile_ = false;
was_lazily_parsed_ = false;
is_skipped_function_ = false;
#ifdef DEBUG
DeclarationScope* outer_declaration_scope =
outer_scope_ ? outer_scope_->GetDeclarationScope() : nullptr;
......@@ -345,6 +349,8 @@ void Scope::SetDefaults() {
force_context_allocation_ = false;
is_declaration_scope_ = false;
must_use_preparsed_scope_data_ = false;
}
bool Scope::HasSimpleParameters() {
......@@ -618,6 +624,7 @@ void DeclarationScope::Analyze(ParseInfo* info, AnalyzeMode mode) {
&RuntimeCallStats::CompileScopeAnalysis);
DCHECK(info->literal() != NULL);
DeclarationScope* scope = info->literal()->scope();
DCHECK(scope->scope_info_.is_null());
Handle<ScopeInfo> outer_scope_info;
if (info->maybe_outer_scope_info().ToHandle(&outer_scope_info)) {
......@@ -653,6 +660,13 @@ void DeclarationScope::Analyze(ParseInfo* info, AnalyzeMode mode) {
// The outer scope is never lazy.
scope->set_should_eager_compile();
if (scope->must_use_preparsed_scope_data_) {
DCHECK(FLAG_preparser_scope_analysis);
DCHECK_NOT_NULL(info->preparsed_scope_data());
DCHECK_EQ(scope->scope_type_, ScopeType::FUNCTION_SCOPE);
info->preparsed_scope_data()->RestoreData(scope);
}
scope->AllocateVariables(info, mode);
// Ensuring that the outer script scope has a scope info avoids having
......@@ -1522,7 +1536,7 @@ void DeclarationScope::AnalyzePartially(
arguments_ = nullptr;
}
if (FLAG_preparser_scope_analysis) {
if (FLAG_preparser_scope_analysis && preparsed_scope_data->Producing()) {
// Store the information needed for allocating the locals of this scope
// and its inner scopes.
preparsed_scope_data->SaveData(this);
......
......@@ -155,6 +155,20 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
Zone* zone() const { return zone_; }
void SetMustUsePreParsedScopeData() {
if (must_use_preparsed_scope_data_) {
return;
}
must_use_preparsed_scope_data_ = true;
if (outer_scope_) {
outer_scope_->SetMustUsePreParsedScopeData();
}
}
bool must_use_preparsed_scope_data() const {
return must_use_preparsed_scope_data_;
}
// ---------------------------------------------------------------------------
// Declarations
......@@ -556,6 +570,8 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
// True if it holds 'var' declarations.
bool is_declaration_scope_ : 1;
bool must_use_preparsed_scope_data_ : 1;
// Create a non-local variable with a given name.
// These variables are looked up dynamically at runtime.
Variable* NonLocal(const AstRawString* name, VariableMode mode);
......@@ -850,6 +866,11 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
void ResetAfterPreparsing(AstValueFactory* ast_value_factory, bool aborted);
bool is_skipped_function() const { return is_skipped_function_; }
void set_is_skipped_function(bool is_skipped_function) {
is_skipped_function_ = is_skipped_function;
}
private:
void AllocateParameter(Variable* var, int index);
......@@ -886,6 +907,7 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
#if DEBUG
bool is_being_lazily_parsed_ : 1;
#endif
bool is_skipped_function_ : 1;
// Parameter list in source order.
ZoneList<Variable*> params_;
......
......@@ -1152,6 +1152,14 @@ MaybeHandle<Code> GetLazyCode(Handle<JSFunction> function) {
ParseInfo parse_info(handle(function->shared()));
Zone compile_zone(isolate->allocator(), ZONE_NAME);
CompilationInfo info(&compile_zone, &parse_info, function);
if (FLAG_preparser_scope_analysis) {
Handle<SharedFunctionInfo> shared(function->shared());
Handle<Script> script(Script::cast(function->shared()->script()));
if (script->HasPreparsedScopeData()) {
parse_info.preparsed_scope_data()->Deserialize(
script->GetPreparsedScopeData());
}
}
Handle<Code> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result, GetUnoptimizedCode(&info, Compiler::CONCURRENT), Code);
......@@ -1234,8 +1242,14 @@ Handle<SharedFunctionInfo> CompileToplevel(CompilationInfo* info) {
PROFILE(isolate, CodeCreateEvent(log_tag, result->abstract_code(), *result,
*script_name));
if (!script.is_null())
if (!script.is_null()) {
script->set_compilation_state(Script::COMPILATION_STATE_COMPILED);
if (FLAG_preparser_scope_analysis) {
Handle<FixedUint32Array> data(
parse_info->preparsed_scope_data()->Serialize(isolate));
script->set_preparsed_scope_data(*data);
}
}
}
return result;
......
......@@ -1111,6 +1111,7 @@ Handle<Script> Factory::NewScript(Handle<String> source) {
script->set_eval_from_position(0);
script->set_shared_function_infos(*empty_fixed_array(), SKIP_WRITE_BARRIER);
script->set_flags(0);
script->set_preparsed_scope_data(heap->empty_fixed_uint32_array());
heap->set_script_list(*WeakFixedArray::Add(script_list(), script));
return script;
......
......@@ -5767,6 +5767,8 @@ ACCESSORS(Script, source_url, Object, kSourceUrlOffset)
ACCESSORS(Script, source_mapping_url, Object, kSourceMappingUrlOffset)
ACCESSORS_CHECKED(Script, wasm_compiled_module, Object, kEvalFromSharedOffset,
this->type() == TYPE_WASM)
ACCESSORS(Script, preparsed_scope_data, FixedTypedArrayBase,
kPreParsedScopeDataOffset)
Script::CompilationType Script::compilation_type() {
return BooleanBit::get(flags(), kCompilationTypeBit) ?
......
......@@ -13260,6 +13260,15 @@ Script::Iterator::Iterator(Isolate* isolate)
Script* Script::Iterator::Next() { return iterator_.Next<Script>(); }
bool Script::HasPreparsedScopeData() const {
return preparsed_scope_data()->length() > 0;
}
Handle<FixedUint32Array> Script::GetPreparsedScopeData() const {
return Handle<FixedUint32Array>::cast(
Handle<FixedTypedArrayBase>(preparsed_scope_data()));
}
SharedFunctionInfo::ScriptIterator::ScriptIterator(Handle<Script> script)
: ScriptIterator(script->GetIsolate(),
handle(script->shared_function_infos())) {}
......
......@@ -6641,6 +6641,8 @@ class Script: public Struct {
// This must only be called if the type of this script is TYPE_WASM.
DECL_ACCESSORS(wasm_compiled_module, Object)
DECL_ACCESSORS(preparsed_scope_data, FixedTypedArrayBase)
// [compilation_type]: how the the script was compiled. Encoded in the
// 'flags' field.
inline CompilationType compilation_type();
......@@ -6727,6 +6729,9 @@ class Script: public Struct {
DISALLOW_COPY_AND_ASSIGN(Iterator);
};
bool HasPreparsedScopeData() const;
Handle<FixedUint32Array> GetPreparsedScopeData() const;
// Dispatched behavior.
DECLARE_PRINTER(Script)
DECLARE_VERIFIER(Script)
......@@ -6748,7 +6753,9 @@ class Script: public Struct {
static const int kFlagsOffset = kSharedFunctionInfosOffset + kPointerSize;
static const int kSourceUrlOffset = kFlagsOffset + kPointerSize;
static const int kSourceMappingUrlOffset = kSourceUrlOffset + kPointerSize;
static const int kSize = kSourceMappingUrlOffset + kPointerSize;
static const int kPreParsedScopeDataOffset =
kSourceMappingUrlOffset + kPointerSize;
static const int kSize = kPreParsedScopeDataOffset + kPointerSize;
private:
// Bit positions in the flags field.
......
......@@ -2826,6 +2826,21 @@ Parser::LazyParsingResult Parser::SkipFunction(
function_scope->RecordEvalCall();
}
SkipFunctionLiterals(data.num_inner_functions);
}
}
// FIXME(marja): There are 3 ways to skip functions now. Unify them.
if (preparsed_scope_data_->Consuming()) {
DCHECK(FLAG_preparser_scope_analysis);
int end_pos = kNoSourcePosition;
if (preparsed_scope_data_->FindFunctionEnd(function_scope->start_position(),
&end_pos)) {
function_scope->set_end_position(end_pos);
function_scope->set_is_skipped_function(true);
function_scope->outer_scope()->SetMustUsePreParsedScopeData();
scanner()->SeekForward(end_pos - 1);
Expect(Token::RBRACE, CHECK_OK_VALUE(kLazyParsingComplete));
// FIXME(marja): SkipFunctionLiterals still needed.
return kLazyParsingComplete;
}
}
......
......@@ -6,6 +6,7 @@
#include "src/ast/scopes.h"
#include "src/ast/variables.h"
#include "src/handles.h"
#include "src/objects-inl.h"
namespace v8 {
......@@ -19,35 +20,119 @@ class VariableMaybeAssignedField
class VariableContextAllocatedField
: public BitField16<bool, VariableMaybeAssignedField::kNext, 1> {};
const int kFunctionDataSize = 3;
} // namespace
/*
Internal data format for the backing store:
------------------------------------
| scope type << only in debug |
| inner_scope_calls_eval_ |
| data end index |
| ---------------------- |
| | data for variables | |
| | ... | |
| ---------------------- |
------------------------------------
------------------------------------
| data for inner scope_1 |
| ... |
------------------------------------
...
------------------------------------
| data for inner scope_n |
| ... |
------------------------------------
<< data end index points here
*/
void PreParsedScopeData::SaveData(Scope* scope) {
size_t old_size = backing_store_.size();
DCHECK(!has_data_);
if (scope->scope_type() == ScopeType::FUNCTION_SCOPE) {
function_index_[scope->start_position()] =
std::make_pair(scope->end_position(), backing_store_.size());
}
if (!ScopeNeedsData(scope)) {
return;
}
#ifdef DEBUG
backing_store_.push_back(scope->scope_type());
#endif
backing_store_.push_back(scope->inner_scope_calls_eval());
// Reserve space for the data end index (which we don't know yet). The end
// index is needed for skipping over data for a function scope when we skip
// parsing of the corresponding function.
size_t data_end_index = backing_store_.size();
backing_store_.push_back(-1);
if (!scope->is_hidden()) {
for (Variable* var : *scope->locals()) {
if (var->mode() == VAR || var->mode() == LET || var->mode() == CONST) {
if (IsDeclaredVariableMode(var->mode())) {
SaveDataForVariable(var);
}
}
}
for (Scope* inner = scope->inner_scope(); inner != nullptr;
inner = inner->sibling()) {
SaveData(inner);
SaveDataForInnerScopes(scope);
backing_store_[data_end_index] = backing_store_.size();
}
void PreParsedScopeData::RestoreData(DeclarationScope* scope) const {
int index = -1;
DCHECK_EQ(scope->scope_type(), ScopeType::FUNCTION_SCOPE);
bool success = FindFunctionData(scope->start_position(), &index);
DCHECK(success);
USE(success);
RestoreData(scope, &index);
}
void PreParsedScopeData::RestoreData(Scope* scope, int* index_ptr) const {
// It's possible that scope is not present in the data at all (since PreParser
// doesn't create the corresponding scope). In this case, the Scope won't
// contain any variables for which we need the data.
if (!ScopeNeedsData(scope) && !IsSkippedFunctionScope(scope)) {
return;
}
if (old_size != backing_store_.size()) {
int& index = *index_ptr;
#ifdef DEBUG
backing_store_.push_back(scope->scope_type());
// Data integrity check.
if (scope->scope_type() == ScopeType::FUNCTION_SCOPE) {
int end_position_from_data = -1;
FindFunctionEnd(scope->start_position(), &end_position_from_data);
DCHECK_EQ(end_position_from_data, scope->end_position());
int index_from_data = -1;
FindFunctionData(scope->start_position(), &index_from_data);
DCHECK_EQ(index_from_data, index);
}
#endif
backing_store_.push_back(scope->inner_scope_calls_eval());
if (IsSkippedFunctionScope(scope)) {
// This scope is a function scope representing a function we want to
// skip. So just skip over its data.
DCHECK(!scope->must_use_preparsed_scope_data());
index = backing_store_[index + 2];
return;
}
}
void PreParsedScopeData::RestoreData(Scope* scope, int* index_ptr) const {
int& index = *index_ptr;
int old_index = index;
DCHECK_EQ(backing_store_[index++], scope->scope_type());
if (backing_store_[index++]) {
scope->RecordEvalCall();
}
int data_end_index = backing_store_[index++];
USE(data_end_index);
if (!scope->is_hidden()) {
for (Variable* var : *scope->locals()) {
......@@ -56,22 +141,65 @@ void PreParsedScopeData::RestoreData(Scope* scope, int* index_ptr) const {
}
}
}
for (Scope* inner = scope->inner_scope(); inner != nullptr;
inner = inner->sibling()) {
RestoreData(inner, index_ptr);
RestoreDataForInnerScopes(scope, index_ptr);
DCHECK_EQ(data_end_index, index);
}
FixedUint32Array* PreParsedScopeData::Serialize(Isolate* isolate) const {
// FIXME(marja): save space by using a byte array and converting
// function_index_ to bytes.
Handle<JSTypedArray> js_array = isolate->factory()->NewJSTypedArray(
UINT32_ELEMENTS,
function_index_.size() * kFunctionDataSize + backing_store_.size() + 1);
FixedUint32Array* array = FixedUint32Array::cast(js_array->elements());
array->set(0, static_cast<uint32_t>(function_index_.size()));
int i = 1;
for (const auto& item : function_index_) {
array->set(i++, item.first);
array->set(i++, item.second.first);
array->set(i++, item.second.second);
}
for (size_t j = 0; j < backing_store_.size(); ++j) {
array->set(i++, static_cast<uint32_t>(backing_store_[j]));
}
return array;
}
if (index != old_index) {
// Some data was read, i.e., there's data for the Scope.
void PreParsedScopeData::Deserialize(Handle<FixedUint32Array> array) {
has_data_ = true;
DCHECK(!array.is_null());
if (array->length() == 0) {
return;
}
int function_count = array->get_scalar(0);
CHECK(array->length() > function_count * kFunctionDataSize);
if (function_count == 0) {
return;
}
int i = 1;
for (; i < function_count * kFunctionDataSize + 1; i += kFunctionDataSize) {
function_index_[array->get_scalar(i)] =
std::make_pair(array->get_scalar(i + 1), array->get_scalar(i + 2));
}
CHECK_EQ(function_index_.size(), function_count);
#ifdef DEBUG
DCHECK_EQ(backing_store_[index++], scope->scope_type());
#endif
backing_store_.reserve(array->length() - i);
for (; i < array->length(); ++i) {
backing_store_.push_back(array->get_scalar(i));
}
}
if (backing_store_[index++]) {
scope->RecordEvalCall();
}
bool PreParsedScopeData::FindFunctionEnd(int start_pos, int* end_pos) const {
auto it = function_index_.find(start_pos);
if (it == function_index_.end()) {
return false;
}
*end_pos = it->second.first;
return true;
}
void PreParsedScopeData::SaveDataForVariable(Variable* var) {
......@@ -84,11 +212,12 @@ void PreParsedScopeData::SaveDataForVariable(Variable* var) {
backing_store_.push_back(name->raw_data()[i]);
}
#endif
int variable_data = VariableIsUsedField::encode(var->is_used()) |
VariableMaybeAssignedField::encode(
var->maybe_assigned() == kMaybeAssigned) |
VariableContextAllocatedField::encode(
var->has_forced_context_allocation());
// FIXME(marja): Only 3 bits needed, not a full byte.
byte variable_data = VariableIsUsedField::encode(var->is_used()) |
VariableMaybeAssignedField::encode(
var->maybe_assigned() == kMaybeAssigned) |
VariableContextAllocatedField::encode(
var->has_forced_context_allocation());
backing_store_.push_back(variable_data);
}
......@@ -103,7 +232,7 @@ void PreParsedScopeData::RestoreDataForVariable(Variable* var,
DCHECK_EQ(backing_store_[index++], name->raw_data()[i]);
}
#endif
int variable_data = backing_store_[index++];
byte variable_data = backing_store_[index++];
if (VariableIsUsedField::decode(variable_data)) {
var->set_is_used();
}
......@@ -115,5 +244,65 @@ void PreParsedScopeData::RestoreDataForVariable(Variable* var,
}
}
void PreParsedScopeData::SaveDataForInnerScopes(Scope* scope) {
// Inner scopes are stored in the reverse order, but we'd like to write the
// data in the logical order. There might be many inner scopes, so we don't
// want to recurse here.
std::vector<Scope*> scopes;
for (Scope* inner = scope->inner_scope(); inner != nullptr;
inner = inner->sibling()) {
scopes.push_back(inner);
}
for (int i = static_cast<int>(scopes.size()) - 1; i >= 0; --i) {
SaveData(scopes[i]);
}
}
void PreParsedScopeData::RestoreDataForInnerScopes(Scope* scope,
int* index_ptr) const {
std::vector<Scope*> scopes;
for (Scope* inner = scope->inner_scope(); inner != nullptr;
inner = inner->sibling()) {
scopes.push_back(inner);
}
for (int i = static_cast<int>(scopes.size()) - 1; i >= 0; --i) {
RestoreData(scopes[i], index_ptr);
}
}
bool PreParsedScopeData::FindFunctionData(int start_pos, int* index) const {
auto it = function_index_.find(start_pos);
if (it == function_index_.end()) {
return false;
}
*index = it->second.second;
return true;
}
bool PreParsedScopeData::ScopeNeedsData(Scope* scope) {
if (scope->scope_type() == ScopeType::FUNCTION_SCOPE) {
return true;
}
if (!scope->is_hidden()) {
for (Variable* var : *scope->locals()) {
if (var->mode() == VAR || var->mode() == LET || var->mode() == CONST) {
return true;
}
}
}
for (Scope* inner = scope->inner_scope(); inner != nullptr;
inner = inner->sibling()) {
if (ScopeNeedsData(inner)) {
return true;
}
}
return false;
}
bool PreParsedScopeData::IsSkippedFunctionScope(Scope* scope) {
return scope->is_declaration_scope() &&
scope->AsDeclarationScope()->is_skipped_function();
}
} // namespace internal
} // namespace v8
......@@ -5,13 +5,56 @@
#ifndef V8_PARSING_PREPARSED_SCOPE_DATA_H_
#define V8_PARSING_PREPARSED_SCOPE_DATA_H_
#include <unordered_map>
#include <vector>
#include "src/globals.h"
#include "src/objects.h"
namespace v8 {
namespace internal {
template <typename T>
class Handle;
/*
Skipping inner functions.
Consider the following code:
(function eager_outer() {
function lazy_inner() {
let a;
function skip_me() { a; }
}
return lazy_inner;
})();
... lazy_inner(); ...
When parsing the code the first time, eager_outer is parsed and lazy_inner
(and everything inside it) is preparsed. When lazy_inner is called, we don't
want to parse or preparse skip_me again. Instead, we want to skip over it,
since it has already been preparsed once.
In order to be able to do this, we need to store the information needed for
allocating the variables in lazy_inner when we preparse it, and then later do
scope allocation based on that data.
We need the following data for each scope in lazy_inner's scope tree:
For each Variable:
- is_used
- maybe_assigned
- has_forced_context_allocation
For each Scope:
- inner_scope_calls_eval_.
PreParsedScopeData implements storing and restoring the above mentioned data.
*/
class PreParsedScopeData {
public:
PreParsedScopeData() {}
......@@ -24,16 +67,36 @@ class PreParsedScopeData {
// Restores the information needed for allocating the Scopes's (and its
// subscopes') variables.
void RestoreData(Scope* scope, int* index_ptr) const;
void RestoreData(DeclarationScope* scope) const;
FixedUint32Array* Serialize(Isolate* isolate) const;
void Deserialize(Handle<FixedUint32Array> array);
bool Consuming() const { return has_data_; }
bool Producing() const { return !has_data_; }
bool FindFunctionEnd(int start_pos, int* end_pos) const;
private:
friend class ScopeTestHelper;
void SaveDataForVariable(Variable* var);
void RestoreDataForVariable(Variable* var, int* index_ptr) const;
void SaveDataForInnerScopes(Scope* scope);
void RestoreDataForInnerScopes(Scope* scope, int* index_ptr) const;
bool FindFunctionData(int start_pos, int* index) const;
static bool ScopeNeedsData(Scope* scope);
static bool IsSkippedFunctionScope(Scope* scope);
// TODO(marja): Make the backing store more efficient once we know exactly
// what data is needed.
std::vector<byte> backing_store_;
// Start pos -> (end pos, index in data)
std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> function_index_;
bool has_data_ = false;
DISALLOW_COPY_AND_ASSIGN(PreParsedScopeData);
};
......
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