Revert "[parser] Skipping inner funcs: Use less memory for variables."
This reverts commit 97ead4338e
.
Reason for revert: makes the PreParserScopeAnalysis test much slower.
Original change's description:
> [parser] Skipping inner funcs: Use less memory for variables.
>
> - Make it possible to store quarter-bytes instead of full bytes.
>
> - Don't store is_used; it can be recovered correctly based on the actual full
> parse (when a lazy function is eventually called) and
> has_forced_scope_allocation.
>
> - With the is_used change, the old testing approach (which compared a scope for
> which we didn't do scope allocation to the baseline) no longer made
> sense. Replaced it with a new testing approach, which is also closer to the
> actual usage.
>
> BUG=v8:5516
>
> Change-Id: I02bac24e482126689dcdbabe8b3a04977be29b0c
> Reviewed-on: https://chromium-review.googlesource.com/725422
> Commit-Queue: Marja Hölttä <marja@chromium.org>
> Reviewed-by: Toon Verwaest <verwaest@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#48828}
TBR=marja@chromium.org,verwaest@chromium.org
Change-Id: I8cb87bcd55462b1cef4444dabb5cbfa2ecb24c7c
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:5516
Reviewed-on: https://chromium-review.googlesource.com/732878
Reviewed-by: Marja Hölttä <marja@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48829}
This commit is contained in:
parent
97ead4338e
commit
271e3b108d
@ -20,16 +20,17 @@ class ScopeCallsSloppyEvalField : public BitField<bool, 0, 1> {};
|
|||||||
class InnerScopeCallsEvalField
|
class InnerScopeCallsEvalField
|
||||||
: public BitField<bool, ScopeCallsSloppyEvalField::kNext, 1> {};
|
: public BitField<bool, ScopeCallsSloppyEvalField::kNext, 1> {};
|
||||||
|
|
||||||
class VariableMaybeAssignedField : public BitField8<bool, 0, 1> {};
|
class VariableIsUsedField : public BitField16<bool, 0, 1> {};
|
||||||
|
class VariableMaybeAssignedField
|
||||||
|
: public BitField16<bool, VariableIsUsedField::kNext, 1> {};
|
||||||
class VariableContextAllocatedField
|
class VariableContextAllocatedField
|
||||||
: public BitField8<bool, VariableMaybeAssignedField::kNext, 1> {};
|
: public BitField16<bool, VariableMaybeAssignedField::kNext, 1> {};
|
||||||
|
|
||||||
const int kMagicValue = 0xc0de0de;
|
const int kMagicValue = 0xc0de0de;
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
const size_t kUint32Size = 5;
|
const size_t kUint32Size = 5;
|
||||||
const size_t kUint8Size = 2;
|
const size_t kUint8Size = 2;
|
||||||
const size_t kQuarterMarker = 0;
|
|
||||||
#else
|
#else
|
||||||
const size_t kUint32Size = 4;
|
const size_t kUint32Size = 4;
|
||||||
const size_t kUint8Size = 1;
|
const size_t kUint8Size = 1;
|
||||||
@ -38,8 +39,8 @@ const size_t kUint8Size = 1;
|
|||||||
const int kPlaceholderSize = kUint32Size;
|
const int kPlaceholderSize = kUint32Size;
|
||||||
const int kSkippableFunctionDataSize = 4 * kUint32Size + 1 * kUint8Size;
|
const int kSkippableFunctionDataSize = 4 * kUint32Size + 1 * kUint8Size;
|
||||||
|
|
||||||
class LanguageField : public BitField8<LanguageMode, 0, 1> {};
|
class LanguageField : public BitField<LanguageMode, 0, 1> {};
|
||||||
class UsesSuperField : public BitField8<bool, LanguageField::kNext, 1> {};
|
class UsesSuperField : public BitField<bool, LanguageField::kNext, 1> {};
|
||||||
STATIC_ASSERT(LanguageModeSize <= LanguageField::kNumValues);
|
STATIC_ASSERT(LanguageModeSize <= LanguageField::kNumValues);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@ -98,7 +99,6 @@ void ProducedPreParsedScopeData::ByteData::WriteUint32(uint32_t data) {
|
|||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
backing_store_.push_back(*d++);
|
backing_store_.push_back(*d++);
|
||||||
}
|
}
|
||||||
free_quarters_in_last_byte_ = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProducedPreParsedScopeData::ByteData::OverwriteFirstUint32(uint32_t data) {
|
void ProducedPreParsedScopeData::ByteData::OverwriteFirstUint32(uint32_t data) {
|
||||||
@ -121,25 +121,6 @@ void ProducedPreParsedScopeData::ByteData::WriteUint8(uint8_t data) {
|
|||||||
backing_store_.push_back(kUint8Size);
|
backing_store_.push_back(kUint8Size);
|
||||||
#endif
|
#endif
|
||||||
backing_store_.push_back(data);
|
backing_store_.push_back(data);
|
||||||
free_quarters_in_last_byte_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProducedPreParsedScopeData::ByteData::WriteQuarter(uint8_t data) {
|
|
||||||
DCHECK_LE(data, 3);
|
|
||||||
if (free_quarters_in_last_byte_ == 0) {
|
|
||||||
#ifdef DEBUG
|
|
||||||
// Save a marker in debug mode.
|
|
||||||
backing_store_.push_back(kQuarterMarker);
|
|
||||||
#endif
|
|
||||||
backing_store_.push_back(0);
|
|
||||||
free_quarters_in_last_byte_ = 3;
|
|
||||||
} else {
|
|
||||||
--free_quarters_in_last_byte_;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t shift_amount = free_quarters_in_last_byte_ * 2;
|
|
||||||
DCHECK_EQ(backing_store_.back() & (3 << shift_amount), 0);
|
|
||||||
backing_store_.back() |= (data << shift_amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<PodArray<uint8_t>> ProducedPreParsedScopeData::ByteData::Serialize(
|
Handle<PodArray<uint8_t>> ProducedPreParsedScopeData::ByteData::Serialize(
|
||||||
@ -235,7 +216,7 @@ void ProducedPreParsedScopeData::AddSkippableFunction(
|
|||||||
uint8_t language_and_super = LanguageField::encode(language_mode) |
|
uint8_t language_and_super = LanguageField::encode(language_mode) |
|
||||||
UsesSuperField::encode(uses_super_property);
|
UsesSuperField::encode(uses_super_property);
|
||||||
|
|
||||||
byte_data_->WriteQuarter(language_and_super);
|
byte_data_->WriteUint8(language_and_super);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProducedPreParsedScopeData::SaveScopeAllocationData(
|
void ProducedPreParsedScopeData::SaveScopeAllocationData(
|
||||||
@ -401,11 +382,14 @@ void ProducedPreParsedScopeData::SaveDataForVariable(Variable* var) {
|
|||||||
byte_data_->WriteUint8(name->raw_data()[i]);
|
byte_data_->WriteUint8(name->raw_data()[i]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
byte variable_data = VariableMaybeAssignedField::encode(
|
// FIXME(marja): Only 3 bits needed, not a full byte.
|
||||||
|
byte variable_data = VariableIsUsedField::encode(var->is_used()) |
|
||||||
|
VariableMaybeAssignedField::encode(
|
||||||
var->maybe_assigned() == kMaybeAssigned) |
|
var->maybe_assigned() == kMaybeAssigned) |
|
||||||
VariableContextAllocatedField::encode(
|
VariableContextAllocatedField::encode(
|
||||||
var->has_forced_context_allocation());
|
var->has_forced_context_allocation());
|
||||||
byte_data_->WriteQuarter(variable_data);
|
|
||||||
|
byte_data_->WriteUint8(variable_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProducedPreParsedScopeData::SaveDataForInnerScopes(Scope* scope) {
|
void ProducedPreParsedScopeData::SaveDataForInnerScopes(Scope* scope) {
|
||||||
@ -445,7 +429,6 @@ int32_t ConsumedPreParsedScopeData::ByteData::ReadUint32() {
|
|||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
*p++ = data_->get(index_++);
|
*p++ = data_->get(index_++);
|
||||||
}
|
}
|
||||||
stored_quarters_ = 0;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,29 +439,9 @@ uint8_t ConsumedPreParsedScopeData::ByteData::ReadUint8() {
|
|||||||
// Check that there indeed is a byte following.
|
// Check that there indeed is a byte following.
|
||||||
DCHECK_EQ(data_->get(index_++), kUint8Size);
|
DCHECK_EQ(data_->get(index_++), kUint8Size);
|
||||||
#endif
|
#endif
|
||||||
stored_quarters_ = 0;
|
|
||||||
return data_->get(index_++);
|
return data_->get(index_++);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ConsumedPreParsedScopeData::ByteData::ReadQuarter() {
|
|
||||||
DCHECK_NOT_NULL(data_);
|
|
||||||
if (stored_quarters_ == 0) {
|
|
||||||
DCHECK_GE(RemainingBytes(), kUint8Size);
|
|
||||||
#ifdef DEBUG
|
|
||||||
// Check that there indeed are quarters following.
|
|
||||||
DCHECK_EQ(data_->get(index_++), kQuarterMarker);
|
|
||||||
#endif
|
|
||||||
stored_byte_ = data_->get(index_++);
|
|
||||||
stored_quarters_ = 4;
|
|
||||||
}
|
|
||||||
// Read the first 2 bits from stored_byte_.
|
|
||||||
uint8_t result = (stored_byte_ >> 6) & 3;
|
|
||||||
DCHECK_LE(result, 3);
|
|
||||||
--stored_quarters_;
|
|
||||||
stored_byte_ <<= 2;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsumedPreParsedScopeData::ConsumedPreParsedScopeData()
|
ConsumedPreParsedScopeData::ConsumedPreParsedScopeData()
|
||||||
: scope_data_(new ByteData()), child_index_(0) {}
|
: scope_data_(new ByteData()), child_index_(0) {}
|
||||||
|
|
||||||
@ -514,7 +477,7 @@ ConsumedPreParsedScopeData::GetDataForSkippableFunction(
|
|||||||
*num_parameters = scope_data_->ReadUint32();
|
*num_parameters = scope_data_->ReadUint32();
|
||||||
*num_inner_functions = scope_data_->ReadUint32();
|
*num_inner_functions = scope_data_->ReadUint32();
|
||||||
|
|
||||||
uint8_t language_and_super = scope_data_->ReadQuarter();
|
uint8_t language_and_super = scope_data_->ReadUint8();
|
||||||
*language_mode = LanguageMode(LanguageField::decode(language_and_super));
|
*language_mode = LanguageMode(LanguageField::decode(language_and_super));
|
||||||
*uses_super_property = UsesSuperField::decode(language_and_super);
|
*uses_super_property = UsesSuperField::decode(language_and_super);
|
||||||
|
|
||||||
@ -555,6 +518,13 @@ void ConsumedPreParsedScopeData::RestoreScopeAllocationData(
|
|||||||
DCHECK_EQ(scope_data_->RemainingBytes(), 0);
|
DCHECK_EQ(scope_data_->RemainingBytes(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConsumedPreParsedScopeData::SkipFunctionDataForTesting() {
|
||||||
|
ByteData::ReadingScope reading_scope(this);
|
||||||
|
scope_data_->SetPosition(0);
|
||||||
|
uint32_t scope_data_start = scope_data_->ReadUint32();
|
||||||
|
scope_data_->SetPosition(scope_data_start);
|
||||||
|
}
|
||||||
|
|
||||||
void ConsumedPreParsedScopeData::RestoreData(Scope* scope) {
|
void ConsumedPreParsedScopeData::RestoreData(Scope* scope) {
|
||||||
if (scope->is_declaration_scope() &&
|
if (scope->is_declaration_scope() &&
|
||||||
scope->AsDeclarationScope()->is_skipped_function()) {
|
scope->AsDeclarationScope()->is_skipped_function()) {
|
||||||
@ -611,12 +581,15 @@ void ConsumedPreParsedScopeData::RestoreDataForVariable(Variable* var) {
|
|||||||
DCHECK_EQ(scope_data_->ReadUint8(), name->raw_data()[i]);
|
DCHECK_EQ(scope_data_->ReadUint8(), name->raw_data()[i]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
uint8_t variable_data = scope_data_->ReadQuarter();
|
CHECK_GE(scope_data_->RemainingBytes(), kUint8Size);
|
||||||
|
uint8_t variable_data = scope_data_->ReadUint8();
|
||||||
|
if (VariableIsUsedField::decode(variable_data)) {
|
||||||
|
var->set_is_used();
|
||||||
|
}
|
||||||
if (VariableMaybeAssignedField::decode(variable_data)) {
|
if (VariableMaybeAssignedField::decode(variable_data)) {
|
||||||
var->set_maybe_assigned();
|
var->set_maybe_assigned();
|
||||||
}
|
}
|
||||||
if (VariableContextAllocatedField::decode(variable_data)) {
|
if (VariableContextAllocatedField::decode(variable_data)) {
|
||||||
var->set_is_used();
|
|
||||||
var->ForceContextAllocation();
|
var->ForceContextAllocation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,12 +69,10 @@ class ProducedPreParsedScopeData : public ZoneObject {
|
|||||||
public:
|
public:
|
||||||
class ByteData : public ZoneObject {
|
class ByteData : public ZoneObject {
|
||||||
public:
|
public:
|
||||||
explicit ByteData(Zone* zone)
|
explicit ByteData(Zone* zone) : backing_store_(zone) {}
|
||||||
: backing_store_(zone), free_quarters_in_last_byte_(0) {}
|
|
||||||
|
|
||||||
void WriteUint32(uint32_t data);
|
void WriteUint32(uint32_t data);
|
||||||
void WriteUint8(uint8_t data);
|
void WriteUint8(uint8_t data);
|
||||||
void WriteQuarter(uint8_t data);
|
|
||||||
|
|
||||||
// For overwriting previously written data at position 0.
|
// For overwriting previously written data at position 0.
|
||||||
void OverwriteFirstUint32(uint32_t data);
|
void OverwriteFirstUint32(uint32_t data);
|
||||||
@ -85,7 +83,6 @@ class ProducedPreParsedScopeData : public ZoneObject {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
ZoneDeque<uint8_t> backing_store_;
|
ZoneDeque<uint8_t> backing_store_;
|
||||||
uint8_t free_quarters_in_last_byte_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a ProducedPreParsedScopeData object which will collect data as we
|
// Create a ProducedPreParsedScopeData object which will collect data as we
|
||||||
@ -184,8 +181,7 @@ class ConsumedPreParsedScopeData {
|
|||||||
public:
|
public:
|
||||||
class ByteData {
|
class ByteData {
|
||||||
public:
|
public:
|
||||||
ByteData()
|
ByteData() : data_(nullptr), index_(0) {}
|
||||||
: data_(nullptr), index_(0), stored_quarters_(0), stored_byte_(0) {}
|
|
||||||
|
|
||||||
// Reading from the ByteData is only allowed when a ReadingScope is on the
|
// Reading from the ByteData is only allowed when a ReadingScope is on the
|
||||||
// stack. This ensures that we have a DisallowHeapAllocation in place
|
// stack. This ensures that we have a DisallowHeapAllocation in place
|
||||||
@ -208,7 +204,6 @@ class ConsumedPreParsedScopeData {
|
|||||||
|
|
||||||
int32_t ReadUint32();
|
int32_t ReadUint32();
|
||||||
uint8_t ReadUint8();
|
uint8_t ReadUint8();
|
||||||
uint8_t ReadQuarter();
|
|
||||||
|
|
||||||
size_t RemainingBytes() const {
|
size_t RemainingBytes() const {
|
||||||
DCHECK_NOT_NULL(data_);
|
DCHECK_NOT_NULL(data_);
|
||||||
@ -218,8 +213,6 @@ class ConsumedPreParsedScopeData {
|
|||||||
// private:
|
// private:
|
||||||
PodArray<uint8_t>* data_;
|
PodArray<uint8_t>* data_;
|
||||||
int index_;
|
int index_;
|
||||||
uint8_t stored_quarters_;
|
|
||||||
uint8_t stored_byte_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ConsumedPreParsedScopeData();
|
ConsumedPreParsedScopeData();
|
||||||
@ -238,6 +231,11 @@ class ConsumedPreParsedScopeData {
|
|||||||
// subscopes') variables.
|
// subscopes') variables.
|
||||||
void RestoreScopeAllocationData(DeclarationScope* scope);
|
void RestoreScopeAllocationData(DeclarationScope* scope);
|
||||||
|
|
||||||
|
// Skips the data about skippable functions, moves straight to the scope
|
||||||
|
// allocation data. Useful for tests which don't want to verify only the scope
|
||||||
|
// allocation data.
|
||||||
|
void SkipFunctionDataForTesting();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RestoreData(Scope* scope);
|
void RestoreData(Scope* scope);
|
||||||
void RestoreDataForVariable(Variable* var);
|
void RestoreDataForVariable(Variable* var);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
#include "src/api.h"
|
|
||||||
#include "src/ast/ast.h"
|
#include "src/ast/ast.h"
|
||||||
#include "src/compiler.h"
|
#include "src/compiler.h"
|
||||||
#include "src/objects-inl.h"
|
#include "src/objects-inl.h"
|
||||||
@ -38,52 +37,85 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
i::FLAG_aggressive_lazy_inner_functions = true;
|
i::FLAG_aggressive_lazy_inner_functions = true;
|
||||||
i::Isolate* isolate = CcTest::i_isolate();
|
i::Isolate* isolate = CcTest::i_isolate();
|
||||||
i::Factory* factory = isolate->factory();
|
i::Factory* factory = isolate->factory();
|
||||||
|
i::HandleScope scope(isolate);
|
||||||
|
LocalContext env;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
const char* code;
|
const char* code;
|
||||||
bool strict_outer;
|
bool strict_outer;
|
||||||
bool strict_test_function;
|
bool strict_test_function;
|
||||||
bool arrow;
|
bool arrow;
|
||||||
|
std::vector<unsigned> location; // "Directions" to the relevant scope.
|
||||||
} outers[] = {
|
} outers[] = {
|
||||||
// Normal case (test function at the laziness boundary):
|
// Normal case (test function at the laziness boundary):
|
||||||
{"function test(%s) { %s function skippable() { } } test;", false, false,
|
{"(function outer() { function test(%s) { %s \n"
|
||||||
false},
|
"function skippable() { } } })();",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
{0, 0}},
|
||||||
|
|
||||||
{"let test2 = function test(%s) { %s function skippable() { } }; test2",
|
{"(function outer() { let test2 = function test(%s) { %s \n"
|
||||||
false, false, false},
|
"function skippable() { } } })();",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
{0, 0}},
|
||||||
|
|
||||||
// Arrow functions (they can never be at the laziness boundary):
|
// Arrow functions (they can never be at the laziness boundary):
|
||||||
{"function test() { (%s) => { %s }; function skippable() { } } test;",
|
{"(function outer() { function inner() { (%s) => { %s } \n"
|
||||||
false, false, true},
|
"function skippable() { } } })();",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
{0, 0}},
|
||||||
|
|
||||||
// Repeat the above mentioned cases with global 'use strict'
|
// Repeat the above mentioned cases w/ outer function declaring itself
|
||||||
{"'use strict'; function test(%s) { %s function skippable() { } } test;",
|
// strict:
|
||||||
true, false, false},
|
{"(function outer() { 'use strict'; function test(%s) { %s \n"
|
||||||
|
"function skippable() { } } })();",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
{0, 0}},
|
||||||
|
|
||||||
{"'use strict'; let test2 = function test(%s) { %s \n"
|
{"(function outer() { 'use strict'; function inner() { "
|
||||||
"function skippable() { } }; test2",
|
"(%s) => { %s } \nfunction skippable() { } } })();",
|
||||||
true, false, false},
|
true,
|
||||||
|
false,
|
||||||
{"'use strict'; function test() { (%s) => { %s };\n"
|
true,
|
||||||
"function skippable() { } } test;",
|
{0, 0}},
|
||||||
true, false, true},
|
|
||||||
|
|
||||||
// ... and with the test function declaring itself strict:
|
// ... and with the test function declaring itself strict:
|
||||||
{"function test(%s) { 'use strict'; %s function skippable() { } } test;",
|
{"(function outer() { function test(%s) { 'use strict'; %s \n"
|
||||||
false, true, false},
|
"function skippable() { } } })();",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
{0, 0}},
|
||||||
|
|
||||||
{"let test2 = function test(%s) { 'use strict'; %s \n"
|
{"(function outer() { function inner() { "
|
||||||
"function skippable() { } }; test2",
|
"(%s) => { 'use strict'; %s } \nfunction skippable() { } } })();",
|
||||||
false, true, false},
|
false,
|
||||||
|
true,
|
||||||
{"function test() { 'use strict'; (%s) => { %s };\n"
|
true,
|
||||||
"function skippable() { } } test;",
|
{0, 0}},
|
||||||
false, true, true},
|
|
||||||
|
|
||||||
// Methods containing skippable functions.
|
// Methods containing skippable functions.
|
||||||
{"class MyClass { test_method(%s) { %s \n"
|
{"class MyClass { constructor(%s) { %s \n"
|
||||||
"function skippable() { } } } let o = new MyClass(); o.test_method;",
|
"function skippable() { } } }",
|
||||||
true, true, false},
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
{0, 0}},
|
||||||
|
|
||||||
|
{"class MyClass { test(%s) { %s \n"
|
||||||
|
"function skippable() { } } }",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
// The default constructor is scope 0 inside the class.
|
||||||
|
{0, 1}},
|
||||||
|
|
||||||
// FIXME(marja): Generators and async functions
|
// FIXME(marja): Generators and async functions
|
||||||
};
|
};
|
||||||
@ -143,20 +175,20 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
|
|
||||||
// Functions.
|
// Functions.
|
||||||
{"function f1() { let var2; }"},
|
{"function f1() { let var2; }"},
|
||||||
{"var var1 = function f1() { let var2; };"},
|
{"var var1 = function f1() { let var2; }"},
|
||||||
{"let var1 = function f1() { let var2; };"},
|
{"let var1 = function f1() { let var2; }"},
|
||||||
{"const var1 = function f1() { let var2; };"},
|
{"const var1 = function f1() { let var2; }"},
|
||||||
{"var var1 = function() { let var2; };"},
|
{"var var1 = function() { let var2; }"},
|
||||||
{"let var1 = function() { let var2; };"},
|
{"let var1 = function() { let var2; }"},
|
||||||
{"const var1 = function() { let var2; };"},
|
{"const var1 = function() { let var2; }"},
|
||||||
|
|
||||||
{"function *f1() { let var2; }"},
|
{"function *f1() { let var2; }"},
|
||||||
{"let var1 = function *f1() { let var2; };"},
|
{"let var1 = function *f1() { let var2; }"},
|
||||||
{"let var1 = function*() { let var2; };"},
|
{"let var1 = function*() { let var2; }"},
|
||||||
|
|
||||||
{"async function f1() { let var2; }"},
|
{"async function f1() { let var2; }"},
|
||||||
{"let var1 = async function f1() { let var2; };"},
|
{"let var1 = async function f1() { let var2; }"},
|
||||||
{"let var1 = async function() { let var2; };"},
|
{"let var1 = async function() { let var2; }"},
|
||||||
|
|
||||||
// Redeclarations.
|
// Redeclarations.
|
||||||
{"var var1; var var1;"},
|
{"var var1; var var1;"},
|
||||||
@ -184,15 +216,15 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
{"arguments = 5;", SKIP_STRICT},
|
{"arguments = 5;", SKIP_STRICT},
|
||||||
{"if (true) { arguments; }"},
|
{"if (true) { arguments; }"},
|
||||||
{"if (true) { arguments = 5; }", SKIP_STRICT},
|
{"if (true) { arguments = 5; }", SKIP_STRICT},
|
||||||
{"() => { arguments; };"},
|
{"() => { arguments; }"},
|
||||||
{"var1, var2, var3", "arguments;"},
|
{"var1, var2, var3", "arguments;"},
|
||||||
{"var1, var2, var3", "arguments = 5;", SKIP_STRICT},
|
{"var1, var2, var3", "arguments = 5;", SKIP_STRICT},
|
||||||
{"var1, var2, var3", "() => { arguments; };"},
|
{"var1, var2, var3", "() => { arguments; }"},
|
||||||
{"var1, var2, var3", "() => { arguments = 5; };", SKIP_STRICT},
|
{"var1, var2, var3", "() => { arguments = 5; }", SKIP_STRICT},
|
||||||
|
|
||||||
{"this;"},
|
{"this;"},
|
||||||
{"if (true) { this; }"},
|
{"if (true) { this; }"},
|
||||||
{"() => { this; };"},
|
{"() => { this; }"},
|
||||||
|
|
||||||
// Variable called "arguments"
|
// Variable called "arguments"
|
||||||
{"var arguments;", SKIP_STRICT},
|
{"var arguments;", SKIP_STRICT},
|
||||||
@ -500,21 +532,21 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
{"{name1: var1}", "name1 = 16;", SKIP_STRICT_FUNCTION},
|
{"{name1: var1}", "name1 = 16;", SKIP_STRICT_FUNCTION},
|
||||||
{"{var1}", "var1 = 16;", SKIP_STRICT_FUNCTION},
|
{"{var1}", "var1 = 16;", SKIP_STRICT_FUNCTION},
|
||||||
|
|
||||||
{"[var1]", "() => { var1; };", SKIP_STRICT_FUNCTION},
|
{"[var1]", "() => { var1; }", SKIP_STRICT_FUNCTION},
|
||||||
{"{name1: var1}", "() => { var1; };", SKIP_STRICT_FUNCTION},
|
{"{name1: var1}", "() => { var1; }", SKIP_STRICT_FUNCTION},
|
||||||
{"{name1: var1}", "() => { name1; };", SKIP_STRICT_FUNCTION},
|
{"{name1: var1}", "() => { name1; }", SKIP_STRICT_FUNCTION},
|
||||||
{"{var1}", "() => { var1; };", SKIP_STRICT_FUNCTION},
|
{"{var1}", "() => { var1; }", SKIP_STRICT_FUNCTION},
|
||||||
|
|
||||||
{"[var1, var2, var3]", "", SKIP_STRICT_FUNCTION},
|
{"[var1, var2, var3]", "", SKIP_STRICT_FUNCTION},
|
||||||
{"{name1: var1, name2: var2, name3: var3}", "", SKIP_STRICT_FUNCTION},
|
{"{name1: var1, name2: var2, name3: var3}", "", SKIP_STRICT_FUNCTION},
|
||||||
{"{var1, var2, var3}", "", SKIP_STRICT_FUNCTION},
|
{"{var1, var2, var3}", "", SKIP_STRICT_FUNCTION},
|
||||||
|
|
||||||
{"[var1, var2, var3]", "() => { var2 = 16;};", SKIP_STRICT_FUNCTION},
|
{"[var1, var2, var3]", "() => { var2 = 16;}", SKIP_STRICT_FUNCTION},
|
||||||
{"{name1: var1, name2: var2, name3: var3}", "() => { var2 = 16;};",
|
{"{name1: var1, name2: var2, name3: var3}", "() => { var2 = 16;}",
|
||||||
SKIP_STRICT_FUNCTION},
|
SKIP_STRICT_FUNCTION},
|
||||||
{"{name1: var1, name2: var2, name3: var3}", "() => { name2 = 16;};",
|
{"{name1: var1, name2: var2, name3: var3}", "() => { name2 = 16;}",
|
||||||
SKIP_STRICT_FUNCTION},
|
SKIP_STRICT_FUNCTION},
|
||||||
{"{var1, var2, var3}", "() => { var2 = 16;};", SKIP_STRICT_FUNCTION},
|
{"{var1, var2, var3}", "() => { var2 = 16;}", SKIP_STRICT_FUNCTION},
|
||||||
|
|
||||||
// Nesting destructuring.
|
// Nesting destructuring.
|
||||||
{"[var1, [var2, var3], {var4, name5: [var5, var6]}]", "",
|
{"[var1, [var2, var3], {var4, name5: [var5, var6]}]", "",
|
||||||
@ -530,9 +562,9 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
|
|
||||||
// Destructuring rest. Because we can.
|
// Destructuring rest. Because we can.
|
||||||
{"var1, ...[var2]", "", SKIP_STRICT_FUNCTION},
|
{"var1, ...[var2]", "", SKIP_STRICT_FUNCTION},
|
||||||
{"var1, ...[var2]", "() => { var2; };", SKIP_STRICT_FUNCTION},
|
{"var1, ...[var2]", "() => { var2; }", SKIP_STRICT_FUNCTION},
|
||||||
{"var1, ...{0: var2}", "", SKIP_STRICT_FUNCTION},
|
{"var1, ...{0: var2}", "", SKIP_STRICT_FUNCTION},
|
||||||
{"var1, ...{0: var2}", "() => { var2; };", SKIP_STRICT_FUNCTION},
|
{"var1, ...{0: var2}", "() => { var2; }", SKIP_STRICT_FUNCTION},
|
||||||
{"var1, ...[]", "", SKIP_STRICT_FUNCTION},
|
{"var1, ...[]", "", SKIP_STRICT_FUNCTION},
|
||||||
{"var1, ...{}", "", SKIP_STRICT_FUNCTION},
|
{"var1, ...{}", "", SKIP_STRICT_FUNCTION},
|
||||||
{"var1, ...[var2, var3]", "", SKIP_STRICT_FUNCTION},
|
{"var1, ...[var2, var3]", "", SKIP_STRICT_FUNCTION},
|
||||||
@ -551,16 +583,16 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
PreciseMaybeAssigned::NO},
|
PreciseMaybeAssigned::NO},
|
||||||
|
|
||||||
// Locals shadowing parameters.
|
// Locals shadowing parameters.
|
||||||
{"var1, var2", "var var1 = 16; () => { var1 = 17; };"},
|
{"var1, var2", "var var1 = 16; () => { var1 = 17; }"},
|
||||||
|
|
||||||
// Locals shadowing destructuring parameters and the rest parameter.
|
// Locals shadowing destructuring parameters and the rest parameter.
|
||||||
{"[var1, var2]", "var var1 = 16; () => { var1 = 17; };",
|
{"[var1, var2]", "var var1 = 16; () => { var1 = 17; }",
|
||||||
SKIP_STRICT_FUNCTION},
|
SKIP_STRICT_FUNCTION},
|
||||||
{"{var1, var2}", "var var1 = 16; () => { var1 = 17; };",
|
{"{var1, var2}", "var var1 = 16; () => { var1 = 17; }",
|
||||||
SKIP_STRICT_FUNCTION},
|
SKIP_STRICT_FUNCTION},
|
||||||
{"var1, var2, ...var3", "var var3 = 16; () => { var3 = 17; };",
|
{"var1, var2, ...var3", "var var3 = 16; () => { var3 = 17; }",
|
||||||
SKIP_STRICT_FUNCTION},
|
SKIP_STRICT_FUNCTION},
|
||||||
{"var1, var2 = var1", "var var1 = 16; () => { var1 = 17; };",
|
{"var1, var2 = var1", "var var1 = 16; () => { var1 = 17; }",
|
||||||
SKIP_STRICT_FUNCTION, PreciseMaybeAssigned::NO},
|
SKIP_STRICT_FUNCTION, PreciseMaybeAssigned::NO},
|
||||||
|
|
||||||
// Hoisted sloppy block function shadowing a parameter.
|
// Hoisted sloppy block function shadowing a parameter.
|
||||||
@ -600,13 +632,13 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
{"class MyClass {}"},
|
{"class MyClass {}"},
|
||||||
{"var1 = class MyClass {};"},
|
{"var1 = class MyClass {}"},
|
||||||
{"var var1 = class MyClass {};"},
|
{"var var1 = class MyClass {}"},
|
||||||
{"let var1 = class MyClass {};"},
|
{"let var1 = class MyClass {}"},
|
||||||
{"const var1 = class MyClass {};"},
|
{"const var1 = class MyClass {}"},
|
||||||
{"var var1 = class {};"},
|
{"var var1 = class {}"},
|
||||||
{"let var1 = class {};"},
|
{"let var1 = class {}"},
|
||||||
{"const var1 = class {};"},
|
{"const var1 = class {}"},
|
||||||
|
|
||||||
{"class MyClass { constructor() {} }"},
|
{"class MyClass { constructor() {} }"},
|
||||||
{"class MyClass { constructor() { var var1; } }"},
|
{"class MyClass { constructor() { var var1; } }"},
|
||||||
@ -673,65 +705,79 @@ TEST(PreParserScopeAnalysis) {
|
|||||||
i::SNPrintF(program, code, inners[inner_ix].params,
|
i::SNPrintF(program, code, inners[inner_ix].params,
|
||||||
inners[inner_ix].source);
|
inners[inner_ix].source);
|
||||||
|
|
||||||
i::HandleScope scope(isolate);
|
|
||||||
LocalContext env;
|
|
||||||
|
|
||||||
i::Handle<i::String> source =
|
i::Handle<i::String> source =
|
||||||
factory->InternalizeUtf8String(program.start());
|
factory->InternalizeUtf8String(program.start());
|
||||||
source->PrintOn(stdout);
|
source->PrintOn(stdout);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
// Compile and run the script to get a pointer to the lazy function.
|
// First compile with the lazy inner function and extract the scope data.
|
||||||
v8::Local<v8::Value> v = CompileRun(program.start());
|
i::Handle<i::Script> script = factory->NewScript(source);
|
||||||
i::Handle<i::Object> o = v8::Utils::OpenHandle(*v);
|
i::ParseInfo lazy_info(script);
|
||||||
i::Handle<i::JSFunction> f = i::Handle<i::JSFunction>::cast(o);
|
|
||||||
i::Handle<i::SharedFunctionInfo> shared = i::handle(f->shared());
|
|
||||||
|
|
||||||
|
// No need to run scope analysis; preparser scope data is produced when
|
||||||
|
// parsing.
|
||||||
|
CHECK(i::parsing::ParseProgram(&lazy_info, isolate));
|
||||||
|
|
||||||
|
// Retrieve the scope data we produced.
|
||||||
|
i::Scope* scope_with_data = i::ScopeTestHelper::FindScope(
|
||||||
|
lazy_info.literal()->scope(), outers[outer_ix].location);
|
||||||
|
i::ProducedPreParsedScopeData* produced_data =
|
||||||
|
scope_with_data->AsDeclarationScope()
|
||||||
|
->produced_preparsed_scope_data();
|
||||||
|
i::MaybeHandle<i::PreParsedScopeData> maybe_produced_data_on_heap =
|
||||||
|
produced_data->Serialize(isolate);
|
||||||
if (inners[inner_ix].bailout == Bailout::BAILOUT_IF_OUTER_SLOPPY &&
|
if (inners[inner_ix].bailout == Bailout::BAILOUT_IF_OUTER_SLOPPY &&
|
||||||
!outers[outer_ix].strict_outer) {
|
!outers[outer_ix].strict_outer) {
|
||||||
CHECK(!shared->HasPreParsedScopeData());
|
DCHECK(maybe_produced_data_on_heap.is_null());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
DCHECK(!maybe_produced_data_on_heap.is_null());
|
||||||
|
i::Handle<i::PreParsedScopeData> produced_data_on_heap =
|
||||||
|
maybe_produced_data_on_heap.ToHandleChecked();
|
||||||
|
|
||||||
CHECK(shared->HasPreParsedScopeData());
|
// Then parse eagerly and check against the scope data.
|
||||||
i::Handle<i::PreParsedScopeData> produced_data_on_heap(
|
script = factory->NewScript(source);
|
||||||
i::PreParsedScopeData::cast(shared->preparsed_scope_data()));
|
|
||||||
|
|
||||||
// Parse the lazy function using the scope data.
|
i::ParseInfo eager_normal(script);
|
||||||
i::ParseInfo using_scope_data(shared);
|
eager_normal.set_allow_lazy_parsing(false);
|
||||||
using_scope_data.set_lazy_compile();
|
|
||||||
using_scope_data.consumed_preparsed_scope_data()->SetData(
|
|
||||||
produced_data_on_heap);
|
|
||||||
CHECK(i::parsing::ParseFunction(&using_scope_data, shared, isolate));
|
|
||||||
|
|
||||||
// Verify that we skipped at least one function inside that scope.
|
CHECK(i::parsing::ParseProgram(&eager_normal, isolate));
|
||||||
i::DeclarationScope* scope_with_skipped_functions =
|
CHECK(i::Compiler::Analyze(&eager_normal));
|
||||||
using_scope_data.literal()->scope();
|
|
||||||
CHECK(i::ScopeTestHelper::HasSkippedFunctionInside(
|
|
||||||
scope_with_skipped_functions));
|
|
||||||
|
|
||||||
// Do scope allocation (based on the preparsed scope data).
|
// Compare the allocation of the variables in two cases: 1) normal scope
|
||||||
i::DeclarationScope::Analyze(&using_scope_data);
|
// allocation 2) allocation based on the preparse data.
|
||||||
|
|
||||||
// Parse the lazy function again eagerly to produce baseline data.
|
i::Scope* normal_scope = i::ScopeTestHelper::FindScope(
|
||||||
i::ParseInfo not_using_scope_data(shared);
|
eager_normal.literal()->scope(), outers[outer_ix].location);
|
||||||
not_using_scope_data.set_lazy_compile();
|
CHECK_NULL(normal_scope->sibling());
|
||||||
CHECK(i::parsing::ParseFunction(¬_using_scope_data, shared, isolate));
|
CHECK(normal_scope->is_function_scope());
|
||||||
|
|
||||||
// Verify that we didn't skip anything (there's no preparsed scope data,
|
i::ParseInfo eager_using_scope_data(script);
|
||||||
// so we cannot skip).
|
eager_using_scope_data.set_allow_lazy_parsing(false);
|
||||||
i::DeclarationScope* scope_without_skipped_functions =
|
|
||||||
not_using_scope_data.literal()->scope();
|
|
||||||
CHECK(!i::ScopeTestHelper::HasSkippedFunctionInside(
|
|
||||||
scope_without_skipped_functions));
|
|
||||||
|
|
||||||
// Do normal scope allocation.
|
CHECK(i::parsing::ParseProgram(&eager_using_scope_data, isolate));
|
||||||
i::DeclarationScope::Analyze(¬_using_scope_data);
|
// Don't run scope analysis (that would obviously decide the correct
|
||||||
|
// allocation for the variables).
|
||||||
|
|
||||||
|
i::Scope* unallocated_scope = i::ScopeTestHelper::FindScope(
|
||||||
|
eager_using_scope_data.literal()->scope(), outers[outer_ix].location);
|
||||||
|
CHECK_NULL(unallocated_scope->sibling());
|
||||||
|
CHECK(unallocated_scope->is_function_scope());
|
||||||
|
|
||||||
|
// Mark all inner functions as "skipped", so that we don't try to restore
|
||||||
|
// data for them. No test should contain eager functions, because we
|
||||||
|
// cannot properly decide whether we have or don't have data for them.
|
||||||
|
i::ScopeTestHelper::MarkInnerFunctionsAsSkipped(unallocated_scope);
|
||||||
|
i::ConsumedPreParsedScopeData* consumed_preparsed_scope_data =
|
||||||
|
lazy_info.consumed_preparsed_scope_data();
|
||||||
|
consumed_preparsed_scope_data->SetData(produced_data_on_heap);
|
||||||
|
consumed_preparsed_scope_data->SkipFunctionDataForTesting();
|
||||||
|
consumed_preparsed_scope_data->RestoreScopeAllocationData(
|
||||||
|
unallocated_scope->AsDeclarationScope());
|
||||||
|
i::ScopeTestHelper::AllocateWithoutVariableResolution(unallocated_scope);
|
||||||
|
|
||||||
// Verify that scope allocation gave the same results when parsing w/ the
|
|
||||||
// scope data (and skipping functions), and when parsing without.
|
|
||||||
i::ScopeTestHelper::CompareScopes(
|
i::ScopeTestHelper::CompareScopes(
|
||||||
scope_without_skipped_functions, scope_with_skipped_functions,
|
normal_scope, unallocated_scope,
|
||||||
inners[inner_ix].precise_maybe_assigned == PreciseMaybeAssigned::YES);
|
inners[inner_ix].precise_maybe_assigned == PreciseMaybeAssigned::YES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -773,20 +819,6 @@ TEST(ProducingAndConsumingByteData) {
|
|||||||
bytes.WriteUint8(0);
|
bytes.WriteUint8(0);
|
||||||
bytes.OverwriteFirstUint32(2017);
|
bytes.OverwriteFirstUint32(2017);
|
||||||
bytes.WriteUint8(100);
|
bytes.WriteUint8(100);
|
||||||
// Write quarter bytes between uint8s and uint32s to verify they're stored
|
|
||||||
// correctly.
|
|
||||||
bytes.WriteQuarter(3);
|
|
||||||
bytes.WriteQuarter(0);
|
|
||||||
bytes.WriteQuarter(2);
|
|
||||||
bytes.WriteQuarter(1);
|
|
||||||
bytes.WriteQuarter(0);
|
|
||||||
bytes.WriteUint8(50);
|
|
||||||
bytes.WriteQuarter(0);
|
|
||||||
bytes.WriteQuarter(1);
|
|
||||||
bytes.WriteQuarter(2);
|
|
||||||
bytes.WriteUint32(50);
|
|
||||||
// End with a lonely quarter.
|
|
||||||
bytes.WriteQuarter(2);
|
|
||||||
|
|
||||||
i::Handle<i::PodArray<uint8_t>> data_on_heap = bytes.Serialize(isolate);
|
i::Handle<i::PodArray<uint8_t>> data_on_heap = bytes.Serialize(isolate);
|
||||||
i::ConsumedPreParsedScopeData::ByteData bytes_for_reading;
|
i::ConsumedPreParsedScopeData::ByteData bytes_for_reading;
|
||||||
@ -801,15 +833,4 @@ TEST(ProducingAndConsumingByteData) {
|
|||||||
CHECK_EQ(bytes_for_reading.ReadUint32(), 0);
|
CHECK_EQ(bytes_for_reading.ReadUint32(), 0);
|
||||||
CHECK_EQ(bytes_for_reading.ReadUint8(), 0);
|
CHECK_EQ(bytes_for_reading.ReadUint8(), 0);
|
||||||
CHECK_EQ(bytes_for_reading.ReadUint8(), 100);
|
CHECK_EQ(bytes_for_reading.ReadUint8(), 100);
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 3);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 0);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 2);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 1);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 0);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadUint8(), 50);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 0);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 1);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 2);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadUint32(), 50);
|
|
||||||
CHECK_EQ(bytes_for_reading.ReadQuarter(), 2);
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@ class ScopeTestHelper {
|
|||||||
return var->scope()->MustAllocateInContext(var);
|
return var->scope()->MustAllocateInContext(var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AllocateWithoutVariableResolution(Scope* scope) {
|
||||||
|
scope->AllocateVariablesRecursively();
|
||||||
|
}
|
||||||
|
|
||||||
static void CompareScopes(Scope* baseline, Scope* scope,
|
static void CompareScopes(Scope* baseline, Scope* scope,
|
||||||
bool precise_maybe_assigned) {
|
bool precise_maybe_assigned) {
|
||||||
CHECK_EQ(baseline->scope_type(), scope->scope_type());
|
CHECK_EQ(baseline->scope_type(), scope->scope_type());
|
||||||
@ -105,20 +109,6 @@ class ScopeTestHelper {
|
|||||||
MarkInnerFunctionsAsSkipped(inner);
|
MarkInnerFunctionsAsSkipped(inner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool HasSkippedFunctionInside(Scope* scope) {
|
|
||||||
if (scope->scope_type() == ScopeType::FUNCTION_SCOPE &&
|
|
||||||
scope->AsDeclarationScope()->is_skipped_function()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (Scope* inner = scope->inner_scope(); inner != nullptr;
|
|
||||||
inner = inner->sibling()) {
|
|
||||||
if (HasSkippedFunctionInside(inner)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
Loading…
Reference in New Issue
Block a user