[esnext] implement spec change to TaggedTemplate callsite caching
Implements the change outlined in https://github.com/tc39/ecma262/pull/890, which has been ratified and pulled into the specification. In particular, template callsite objects are no longer kept in a global, eternal Map, but are instead associated with their callsite, which can be collected. This prevents a memory leak incurred by TaggedTemplate calls. Changes, summarized: - Remove the TemplateMap and TemplateMapShape objects, instead caching template objects in the feedback vector. - Remove the `hash` member of TemplateObjectDescriptor, and the Equals method (used by TemplateMap) - Add a new FeedbackSlotKind (kTemplateObject), which behaves similarly to FeedbackSlotKind::kLiteral, but prevents eval caching. This ensures that a new feedback vector is always created for eval() containing tagged templates, even when the CompilationCache is used. - GetTemplateObject bytecode now takes a feedback index, and only calls into the runtime if the feedback is Smi::kZero (uninitialized). BUG=v8:3230, v8:2891 R=littledan@chromium.org, yangguo@chromium.org, bmeurer@chromium.org, rmcilroy@chromium.org Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng Change-Id: I7827bc148d3d93e2b056ebf63dd624da196ad423 Reviewed-on: https://chromium-review.googlesource.com/624564 Commit-Queue: Caitlin Potter <caitp@igalia.com> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Cr-Commit-Position: refs/heads/master@{#51248}
This commit is contained in:
parent
ae0447fa48
commit
d3ca0d0050
@ -683,8 +683,8 @@ Handle<TemplateObjectDescription> GetTemplateObject::GetOrBuildDescription(
|
||||
}
|
||||
}
|
||||
}
|
||||
return isolate->factory()->NewTemplateObjectDescription(
|
||||
this->hash(), raw_strings, cooked_strings);
|
||||
return isolate->factory()->NewTemplateObjectDescription(raw_strings,
|
||||
cooked_strings);
|
||||
}
|
||||
|
||||
static bool IsCommutativeOperationWithSmiLiteral(Token::Value op) {
|
||||
|
@ -2633,7 +2633,6 @@ class GetTemplateObject final : public Expression {
|
||||
const ZoneList<const AstRawString*>* raw_strings() const {
|
||||
return raw_strings_;
|
||||
}
|
||||
int hash() const { return hash_; }
|
||||
|
||||
Handle<TemplateObjectDescription> GetOrBuildDescription(Isolate* isolate);
|
||||
|
||||
@ -2641,16 +2640,13 @@ class GetTemplateObject final : public Expression {
|
||||
friend class AstNodeFactory;
|
||||
|
||||
GetTemplateObject(const ZoneList<const AstRawString*>* cooked_strings,
|
||||
const ZoneList<const AstRawString*>* raw_strings, int hash,
|
||||
int pos)
|
||||
const ZoneList<const AstRawString*>* raw_strings, int pos)
|
||||
: Expression(pos, kGetTemplateObject),
|
||||
cooked_strings_(cooked_strings),
|
||||
raw_strings_(raw_strings),
|
||||
hash_(hash) {}
|
||||
raw_strings_(raw_strings) {}
|
||||
|
||||
const ZoneList<const AstRawString*>* cooked_strings_;
|
||||
const ZoneList<const AstRawString*>* raw_strings_;
|
||||
int hash_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -3225,9 +3221,8 @@ class AstNodeFactory final BASE_EMBEDDED {
|
||||
|
||||
GetTemplateObject* NewGetTemplateObject(
|
||||
const ZoneList<const AstRawString*>* cooked_strings,
|
||||
const ZoneList<const AstRawString*>* raw_strings, int hash, int pos) {
|
||||
return new (zone_)
|
||||
GetTemplateObject(cooked_strings, raw_strings, hash, pos);
|
||||
const ZoneList<const AstRawString*>* raw_strings, int pos) {
|
||||
return new (zone_) GetTemplateObject(cooked_strings, raw_strings, pos);
|
||||
}
|
||||
|
||||
ImportCallExpression* NewImportCallExpression(Expression* args, int pos) {
|
||||
|
@ -316,6 +316,9 @@ void CompilationCache::PutEval(Handle<String> source,
|
||||
Handle<Cell> literals, int position) {
|
||||
if (!IsEnabled()) return;
|
||||
|
||||
// TemplateObject feedback slots disable eval caching
|
||||
if (function_info->feedback_metadata()->HasTemplateObjectSlot()) return;
|
||||
|
||||
HandleScope scope(isolate());
|
||||
if (context->IsNativeContext()) {
|
||||
eval_global_.Put(source, outer_info, function_info, context, literals,
|
||||
|
@ -1584,12 +1584,21 @@ void BytecodeGraphBuilder::VisitGetTemplateObject() {
|
||||
Handle<TemplateObjectDescription> description =
|
||||
Handle<TemplateObjectDescription>::cast(
|
||||
bytecode_iterator().GetConstantForIndexOperand(0));
|
||||
// It's not observable when the template object is created, so we
|
||||
// can just create it eagerly during graph building and bake in
|
||||
// the JSArray constant here.
|
||||
Node* template_object =
|
||||
jsgraph()->HeapConstant(TemplateObjectDescription::GetTemplateObject(
|
||||
description, native_context()));
|
||||
FeedbackSlot slot = bytecode_iterator().GetSlotOperand(1);
|
||||
FeedbackNexus nexus(feedback_vector(), slot);
|
||||
|
||||
Handle<JSArray> cached_value;
|
||||
if (nexus.GetFeedback() == Smi::kZero) {
|
||||
// It's not observable when the template object is created, so we
|
||||
// can just create it eagerly during graph building and bake in
|
||||
// the JSArray constant here.
|
||||
cached_value = TemplateObjectDescription::CreateTemplateObject(description);
|
||||
nexus.vector()->Set(slot, *cached_value);
|
||||
} else {
|
||||
cached_value = handle(JSArray::cast(nexus.GetFeedback()));
|
||||
}
|
||||
|
||||
Node* template_object = jsgraph()->HeapConstant(cached_value);
|
||||
environment()->BindAccumulator(template_object);
|
||||
}
|
||||
|
||||
|
@ -406,7 +406,6 @@ enum ContextLookupFlags {
|
||||
V(WASM_MEMORY_CONSTRUCTOR_INDEX, JSFunction, wasm_memory_constructor) \
|
||||
V(WASM_MODULE_CONSTRUCTOR_INDEX, JSFunction, wasm_module_constructor) \
|
||||
V(WASM_TABLE_CONSTRUCTOR_INDEX, JSFunction, wasm_table_constructor) \
|
||||
V(TEMPLATE_MAP_INDEX, HeapObject, template_map) \
|
||||
V(TYPED_ARRAY_FUN_INDEX, JSFunction, typed_array_function) \
|
||||
V(TYPED_ARRAY_PROTOTYPE_INDEX, JSObject, typed_array_prototype) \
|
||||
V(UINT16_ARRAY_FUN_INDEX, JSFunction, uint16_array_fun) \
|
||||
|
@ -149,13 +149,11 @@ Handle<ConstantElementsPair> Factory::NewConstantElementsPair(
|
||||
}
|
||||
|
||||
Handle<TemplateObjectDescription> Factory::NewTemplateObjectDescription(
|
||||
int hash, Handle<FixedArray> raw_strings,
|
||||
Handle<FixedArray> cooked_strings) {
|
||||
Handle<FixedArray> raw_strings, Handle<FixedArray> cooked_strings) {
|
||||
DCHECK_EQ(raw_strings->length(), cooked_strings->length());
|
||||
DCHECK_LT(0, raw_strings->length());
|
||||
Handle<TemplateObjectDescription> result =
|
||||
Handle<TemplateObjectDescription>::cast(NewStruct(TUPLE3_TYPE, TENURED));
|
||||
result->set_hash(hash);
|
||||
Handle<TemplateObjectDescription>::cast(NewStruct(TUPLE2_TYPE, TENURED));
|
||||
result->set_raw_strings(*raw_strings);
|
||||
result->set_cooked_strings(*cooked_strings);
|
||||
return result;
|
||||
|
@ -165,8 +165,7 @@ class V8_EXPORT_PRIVATE Factory final {
|
||||
|
||||
// Create a new TemplateObjectDescription struct.
|
||||
Handle<TemplateObjectDescription> NewTemplateObjectDescription(
|
||||
int hash, Handle<FixedArray> raw_strings,
|
||||
Handle<FixedArray> cooked_strings);
|
||||
Handle<FixedArray> raw_strings, Handle<FixedArray> cooked_strings);
|
||||
|
||||
// Create a pre-tenured empty AccessorPair.
|
||||
Handle<AccessorPair> NewAccessorPair();
|
||||
|
@ -35,6 +35,16 @@ int FeedbackMetadata::slot_count() const {
|
||||
return Smi::ToInt(get(kSlotsCountIndex));
|
||||
}
|
||||
|
||||
bool FeedbackMetadata::HasTemplateObjectSlot() const {
|
||||
DisallowHeapAllocation no_gc;
|
||||
FeedbackMetadataIterator iter(const_cast<FeedbackMetadata*>(this));
|
||||
while (iter.HasNext()) {
|
||||
iter.Next();
|
||||
if (iter.kind() == FeedbackSlotKind::kTemplateObject) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
FeedbackVector* FeedbackVector::cast(Object* obj) {
|
||||
DCHECK(obj->IsFeedbackVector());
|
||||
@ -48,6 +58,7 @@ int FeedbackMetadata::GetSlotSize(FeedbackSlotKind kind) {
|
||||
case FeedbackSlotKind::kCompareOp:
|
||||
case FeedbackSlotKind::kBinaryOp:
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
case FeedbackSlotKind::kCreateClosure:
|
||||
case FeedbackSlotKind::kTypeProfile:
|
||||
return 1;
|
||||
@ -307,6 +318,7 @@ void FeedbackVector::ComputeCounts(int* with_type_info, int* generic,
|
||||
}
|
||||
case FeedbackSlotKind::kCreateClosure:
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
break;
|
||||
case FeedbackSlotKind::kInvalid:
|
||||
case FeedbackSlotKind::kKindsNumber:
|
||||
|
@ -173,6 +173,8 @@ const char* FeedbackMetadata::Kind2String(FeedbackSlotKind kind) {
|
||||
return "kCreateClosure";
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
return "Literal";
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
return "TemplateObject";
|
||||
case FeedbackSlotKind::kTypeProfile:
|
||||
return "TypeProfile";
|
||||
case FeedbackSlotKind::kForIn:
|
||||
@ -255,6 +257,7 @@ Handle<FeedbackVector> FeedbackVector::New(Isolate* isolate,
|
||||
break;
|
||||
}
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
vector->set(index, Smi::kZero, SKIP_WRITE_BARRIER);
|
||||
break;
|
||||
case FeedbackSlotKind::kCall:
|
||||
@ -415,6 +418,7 @@ void FeedbackNexus::ConfigureUninitialized() {
|
||||
switch (kind()) {
|
||||
case FeedbackSlotKind::kCreateClosure:
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
break;
|
||||
|
||||
case FeedbackSlotKind::kStoreGlobalSloppy:
|
||||
@ -479,6 +483,7 @@ bool FeedbackNexus::Clear() {
|
||||
break;
|
||||
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
SetFeedback(Smi::kZero, SKIP_WRITE_BARRIER);
|
||||
feedback_updated = true;
|
||||
break;
|
||||
@ -549,6 +554,7 @@ InlineCacheState FeedbackNexus::StateFromFeedback() const {
|
||||
switch (kind()) {
|
||||
case FeedbackSlotKind::kCreateClosure:
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
// CreateClosure and literal slots don't have a notion of state.
|
||||
UNREACHABLE();
|
||||
break;
|
||||
|
@ -48,6 +48,7 @@ enum class FeedbackSlotKind {
|
||||
kTypeProfile,
|
||||
kCreateClosure,
|
||||
kLiteral,
|
||||
kTemplateObject,
|
||||
kForIn,
|
||||
kInstanceOf,
|
||||
|
||||
@ -379,6 +380,9 @@ class V8_EXPORT_PRIVATE FeedbackVectorSpec {
|
||||
}
|
||||
|
||||
FeedbackSlot AddLiteralSlot() { return AddSlot(FeedbackSlotKind::kLiteral); }
|
||||
FeedbackSlot AddTemplateObjectSlot() {
|
||||
return AddSlot(FeedbackSlotKind::kTemplateObject);
|
||||
}
|
||||
|
||||
FeedbackSlot AddStoreDataPropertyInLiteralICSlot() {
|
||||
return AddSlot(FeedbackSlotKind::kStoreDataPropertyInLiteral);
|
||||
@ -427,6 +431,8 @@ class FeedbackMetadata : public FixedArray {
|
||||
// Returns number of slots in the vector.
|
||||
inline int slot_count() const;
|
||||
|
||||
inline bool HasTemplateObjectSlot() const;
|
||||
|
||||
// Returns slot kind for given slot.
|
||||
FeedbackSlotKind GetKind(FeedbackSlot slot) const;
|
||||
|
||||
|
@ -973,8 +973,8 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CreateEmptyObjectLiteral() {
|
||||
}
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::GetTemplateObject(
|
||||
size_t template_object_description_entry) {
|
||||
OutputGetTemplateObject(template_object_description_entry);
|
||||
size_t template_object_description_entry, int feedback_slot) {
|
||||
OutputGetTemplateObject(template_object_description_entry, feedback_slot);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,7 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final {
|
||||
// Gets or creates the template for a TemplateObjectDescription which will
|
||||
// be inserted at constant pool index |template_object_description_entry|.
|
||||
BytecodeArrayBuilder& GetTemplateObject(
|
||||
size_t template_object_description_entry);
|
||||
size_t template_object_description_entry, int feedback_slot);
|
||||
|
||||
// Push the context in accumulator as the new context, and store in register
|
||||
// |context|.
|
||||
|
@ -4174,7 +4174,8 @@ void BytecodeGenerator::VisitGetTemplateObject(GetTemplateObject* expr) {
|
||||
builder()->SetExpressionPosition(expr);
|
||||
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
|
||||
template_objects_.push_back(std::make_pair(expr, entry));
|
||||
builder()->GetTemplateObject(entry);
|
||||
FeedbackSlot literal_slot = feedback_spec()->AddTemplateObjectSlot();
|
||||
builder()->GetTemplateObject(entry, feedback_index(literal_slot));
|
||||
}
|
||||
|
||||
void BytecodeGenerator::VisitThisFunction(ThisFunction* expr) {
|
||||
|
@ -233,7 +233,8 @@ namespace interpreter {
|
||||
V(CreateEmptyObjectLiteral, AccumulatorUse::kWrite) \
|
||||
\
|
||||
/* Tagged templates */ \
|
||||
V(GetTemplateObject, AccumulatorUse::kWrite, OperandType::kIdx) \
|
||||
V(GetTemplateObject, AccumulatorUse::kWrite, OperandType::kIdx, \
|
||||
OperandType::kIdx) \
|
||||
\
|
||||
/* Closure allocation */ \
|
||||
V(CreateClosure, AccumulatorUse::kWrite, OperandType::kIdx, \
|
||||
|
@ -2417,18 +2417,34 @@ IGNITION_HANDLER(CreateEmptyObjectLiteral, InterpreterAssembler) {
|
||||
Dispatch();
|
||||
}
|
||||
|
||||
// GetTemplateObject
|
||||
// GetTemplateObject <descriptor_idx> <literal_idx>
|
||||
//
|
||||
// Creates the template to pass for tagged templates and returns it in the
|
||||
// accumulator, creating and caching the site object on-demand as per the
|
||||
// specification.
|
||||
IGNITION_HANDLER(GetTemplateObject, InterpreterAssembler) {
|
||||
Node* description = LoadConstantPoolEntryAtOperandIndex(0);
|
||||
Node* context = GetContext();
|
||||
Node* feedback_vector = LoadFeedbackVector();
|
||||
Node* slot = BytecodeOperandIdx(1);
|
||||
Node* cached_value =
|
||||
LoadFeedbackVectorSlot(feedback_vector, slot, 0, INTPTR_PARAMETERS);
|
||||
|
||||
Node* result = CallRuntime(Runtime::kGetTemplateObject, context, description);
|
||||
SetAccumulator(result);
|
||||
Label call_runtime(this, Label::kDeferred);
|
||||
GotoIf(WordEqual(cached_value, SmiConstant(0)), &call_runtime);
|
||||
|
||||
SetAccumulator(cached_value);
|
||||
Dispatch();
|
||||
|
||||
BIND(&call_runtime);
|
||||
{
|
||||
Node* description = LoadConstantPoolEntryAtOperandIndex(0);
|
||||
Node* context = GetContext();
|
||||
Node* result =
|
||||
CallRuntime(Runtime::kCreateTemplateObject, context, description);
|
||||
StoreFeedbackVectorSlot(feedback_vector, slot, result, UPDATE_WRITE_BARRIER,
|
||||
0, INTPTR_PARAMETERS);
|
||||
SetAccumulator(result);
|
||||
Dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
// CreateClosure <index> <slot> <tenured>
|
||||
|
@ -112,8 +112,7 @@ TYPE_CHECKER(PropertyDescriptorObject, FIXED_ARRAY_TYPE)
|
||||
TYPE_CHECKER(SmallOrderedHashMap, SMALL_ORDERED_HASH_MAP_TYPE)
|
||||
TYPE_CHECKER(SmallOrderedHashSet, SMALL_ORDERED_HASH_SET_TYPE)
|
||||
TYPE_CHECKER(SourcePositionTableWithFrameCache, TUPLE2_TYPE)
|
||||
TYPE_CHECKER(TemplateMap, HASH_TABLE_TYPE)
|
||||
TYPE_CHECKER(TemplateObjectDescription, TUPLE3_TYPE)
|
||||
TYPE_CHECKER(TemplateObjectDescription, TUPLE2_TYPE)
|
||||
TYPE_CHECKER(TransitionArray, TRANSITION_ARRAY_TYPE)
|
||||
TYPE_CHECKER(WasmInstanceObject, WASM_INSTANCE_TYPE)
|
||||
TYPE_CHECKER(WasmMemoryObject, WASM_MEMORY_TYPE)
|
||||
@ -624,7 +623,6 @@ CAST_ACCESSOR(StringSet)
|
||||
CAST_ACCESSOR(StringTable)
|
||||
CAST_ACCESSOR(Struct)
|
||||
CAST_ACCESSOR(TemplateInfo)
|
||||
CAST_ACCESSOR(TemplateMap)
|
||||
CAST_ACCESSOR(TemplateObjectDescription)
|
||||
CAST_ACCESSOR(Tuple2)
|
||||
CAST_ACCESSOR(Tuple3)
|
||||
@ -2365,7 +2363,6 @@ bool ConstantElementsPair::is_empty() const {
|
||||
return constant_values()->length() == 0;
|
||||
}
|
||||
|
||||
SMI_ACCESSORS(TemplateObjectDescription, hash, kHashOffset)
|
||||
ACCESSORS(TemplateObjectDescription, raw_strings, FixedArray, kRawStringsOffset)
|
||||
ACCESSORS(TemplateObjectDescription, cooked_strings, FixedArray,
|
||||
kCookedStringsOffset)
|
||||
|
@ -843,6 +843,7 @@ void FeedbackNexus::Print(std::ostream& os) { // NOLINT
|
||||
}
|
||||
case FeedbackSlotKind::kCreateClosure:
|
||||
case FeedbackSlotKind::kLiteral:
|
||||
case FeedbackSlotKind::kTemplateObject:
|
||||
case FeedbackSlotKind::kTypeProfile:
|
||||
break;
|
||||
case FeedbackSlotKind::kInvalid:
|
||||
|
@ -16455,8 +16455,6 @@ template class HashTable<ObjectHashTable, ObjectHashTableShape>;
|
||||
|
||||
template class HashTable<WeakHashTable, WeakHashTableShape>;
|
||||
|
||||
template class HashTable<TemplateMap, TemplateMapShape>;
|
||||
|
||||
template class Dictionary<NameDictionary, NameDictionaryShape>;
|
||||
|
||||
template class Dictionary<GlobalDictionary, GlobalDictionaryShape>;
|
||||
|
@ -958,7 +958,6 @@ class FeedbackVector;
|
||||
class WeakCell;
|
||||
class TransitionArray;
|
||||
class TemplateList;
|
||||
class TemplateMap;
|
||||
template <typename T>
|
||||
class ZoneForwardList;
|
||||
|
||||
@ -1114,7 +1113,6 @@ template <class C> inline bool Is(Object* obj);
|
||||
V(Symbol) \
|
||||
V(TemplateInfo) \
|
||||
V(TemplateList) \
|
||||
V(TemplateMap) \
|
||||
V(TemplateObjectDescription) \
|
||||
V(ThinString) \
|
||||
V(TransitionArray) \
|
||||
|
@ -12,117 +12,41 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
bool TemplateObjectDescription::Equals(
|
||||
TemplateObjectDescription const* that) const {
|
||||
if (this->raw_strings()->length() == that->raw_strings()->length()) {
|
||||
for (int i = this->raw_strings()->length(); --i >= 0;) {
|
||||
if (this->raw_strings()->get(i) != that->raw_strings()->get(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
Handle<JSArray> TemplateObjectDescription::GetTemplateObject(
|
||||
Handle<TemplateObjectDescription> description,
|
||||
Handle<Context> native_context) {
|
||||
DCHECK(native_context->IsNativeContext());
|
||||
Isolate* const isolate = native_context->GetIsolate();
|
||||
Handle<JSArray> TemplateObjectDescription::CreateTemplateObject(
|
||||
Handle<TemplateObjectDescription> description) {
|
||||
Isolate* const isolate = description->GetIsolate();
|
||||
|
||||
// Check if we already have a [[TemplateMap]] for the {native_context},
|
||||
// and if not, just allocate one on the fly (which will be set below).
|
||||
Handle<TemplateMap> template_map =
|
||||
native_context->template_map()->IsUndefined(isolate)
|
||||
? TemplateMap::New(isolate)
|
||||
: handle(TemplateMap::cast(native_context->template_map()), isolate);
|
||||
// Create the raw object from the {raw_strings}.
|
||||
Handle<FixedArray> raw_strings(description->raw_strings(), isolate);
|
||||
Handle<JSArray> raw_object = isolate->factory()->NewJSArrayWithElements(
|
||||
raw_strings, PACKED_ELEMENTS, raw_strings->length(), TENURED);
|
||||
|
||||
// Check if we already have an appropriate entry.
|
||||
Handle<JSArray> template_object;
|
||||
if (!TemplateMap::Lookup(template_map, description)
|
||||
.ToHandle(&template_object)) {
|
||||
// Create the raw object from the {raw_strings}.
|
||||
Handle<FixedArray> raw_strings(description->raw_strings(), isolate);
|
||||
Handle<JSArray> raw_object = isolate->factory()->NewJSArrayWithElements(
|
||||
raw_strings, PACKED_ELEMENTS, raw_strings->length(), TENURED);
|
||||
// Create the template object from the {cooked_strings}.
|
||||
Handle<FixedArray> cooked_strings(description->cooked_strings(), isolate);
|
||||
Handle<JSArray> template_object = isolate->factory()->NewJSArrayWithElements(
|
||||
cooked_strings, PACKED_ELEMENTS, cooked_strings->length(), TENURED);
|
||||
|
||||
// Create the template object from the {cooked_strings}.
|
||||
Handle<FixedArray> cooked_strings(description->cooked_strings(), isolate);
|
||||
template_object = isolate->factory()->NewJSArrayWithElements(
|
||||
cooked_strings, PACKED_ELEMENTS, cooked_strings->length(), TENURED);
|
||||
// Freeze the {raw_object}.
|
||||
JSObject::SetIntegrityLevel(raw_object, FROZEN, kThrowOnError).ToChecked();
|
||||
|
||||
// Freeze the {raw_object}.
|
||||
JSObject::SetIntegrityLevel(raw_object, FROZEN, kThrowOnError).ToChecked();
|
||||
// Install a "raw" data property for {raw_object} on {template_object}.
|
||||
PropertyDescriptor raw_desc;
|
||||
raw_desc.set_value(raw_object);
|
||||
raw_desc.set_configurable(false);
|
||||
raw_desc.set_enumerable(false);
|
||||
raw_desc.set_writable(false);
|
||||
JSArray::DefineOwnProperty(isolate, template_object,
|
||||
isolate->factory()->raw_string(), &raw_desc,
|
||||
kThrowOnError)
|
||||
.ToChecked();
|
||||
|
||||
// Install a "raw" data property for {raw_object} on {template_object}.
|
||||
PropertyDescriptor raw_desc;
|
||||
raw_desc.set_value(raw_object);
|
||||
raw_desc.set_configurable(false);
|
||||
raw_desc.set_enumerable(false);
|
||||
raw_desc.set_writable(false);
|
||||
JSArray::DefineOwnProperty(isolate, template_object,
|
||||
isolate->factory()->raw_string(), &raw_desc,
|
||||
kThrowOnError)
|
||||
.ToChecked();
|
||||
|
||||
// Freeze the {template_object} as well.
|
||||
JSObject::SetIntegrityLevel(template_object, FROZEN, kThrowOnError)
|
||||
.ToChecked();
|
||||
|
||||
// Remember the {template_object} in the {template_map}.
|
||||
template_map = TemplateMap::Add(template_map, description, template_object);
|
||||
native_context->set_template_map(*template_map);
|
||||
}
|
||||
// Freeze the {template_object} as well.
|
||||
JSObject::SetIntegrityLevel(template_object, FROZEN, kThrowOnError)
|
||||
.ToChecked();
|
||||
|
||||
return template_object;
|
||||
}
|
||||
|
||||
// static
|
||||
bool TemplateMapShape::IsMatch(TemplateObjectDescription* key, Object* value) {
|
||||
return key->Equals(TemplateObjectDescription::cast(value));
|
||||
}
|
||||
|
||||
// static
|
||||
uint32_t TemplateMapShape::Hash(Isolate* isolate,
|
||||
TemplateObjectDescription* key) {
|
||||
return key->hash();
|
||||
}
|
||||
|
||||
// static
|
||||
uint32_t TemplateMapShape::HashForObject(Isolate* isolate, Object* object) {
|
||||
return Hash(isolate, TemplateObjectDescription::cast(object));
|
||||
}
|
||||
|
||||
// static
|
||||
Handle<TemplateMap> TemplateMap::New(Isolate* isolate) {
|
||||
return HashTable::New(isolate, 0);
|
||||
}
|
||||
|
||||
// static
|
||||
MaybeHandle<JSArray> TemplateMap::Lookup(
|
||||
Handle<TemplateMap> template_map, Handle<TemplateObjectDescription> key) {
|
||||
int const entry = template_map->FindEntry(*key);
|
||||
if (entry == kNotFound) return MaybeHandle<JSArray>();
|
||||
int const index = EntryToIndex(entry);
|
||||
return handle(JSArray::cast(template_map->get(index + 1)));
|
||||
}
|
||||
|
||||
// static
|
||||
Handle<TemplateMap> TemplateMap::Add(Handle<TemplateMap> template_map,
|
||||
Handle<TemplateObjectDescription> key,
|
||||
Handle<JSArray> value) {
|
||||
DCHECK_EQ(kNotFound, template_map->FindEntry(*key));
|
||||
template_map = EnsureCapacity(template_map, 1);
|
||||
uint32_t const hash = ShapeT::Hash(key->GetIsolate(), *key);
|
||||
int const entry = template_map->FindInsertionEntry(hash);
|
||||
int const index = EntryToIndex(entry);
|
||||
template_map->set(index + 0, *key);
|
||||
template_map->set(index + 1, *value);
|
||||
template_map->ElementAdded();
|
||||
return template_map;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -16,61 +16,25 @@ namespace internal {
|
||||
|
||||
// TemplateObjectDescription is a triple of hash, raw strings and cooked
|
||||
// strings for tagged template literals. Used to communicate with the runtime
|
||||
// for template object creation within the {Runtime_GetTemplateObject} method.
|
||||
class TemplateObjectDescription final : public Tuple3 {
|
||||
// for template object creation within the {Runtime_CreateTemplateObject}
|
||||
// method.
|
||||
class TemplateObjectDescription final : public Tuple2 {
|
||||
public:
|
||||
DECL_INT_ACCESSORS(hash)
|
||||
DECL_ACCESSORS(raw_strings, FixedArray)
|
||||
DECL_ACCESSORS(cooked_strings, FixedArray)
|
||||
|
||||
bool Equals(TemplateObjectDescription const* that) const;
|
||||
|
||||
static Handle<JSArray> GetTemplateObject(
|
||||
Handle<TemplateObjectDescription> description,
|
||||
Handle<Context> native_context);
|
||||
static Handle<JSArray> CreateTemplateObject(
|
||||
Handle<TemplateObjectDescription> description);
|
||||
|
||||
DECL_CAST(TemplateObjectDescription)
|
||||
|
||||
static constexpr int kHashOffset = kValue1Offset;
|
||||
static constexpr int kRawStringsOffset = kValue2Offset;
|
||||
static constexpr int kCookedStringsOffset = kValue3Offset;
|
||||
static constexpr int kRawStringsOffset = kValue1Offset;
|
||||
static constexpr int kCookedStringsOffset = kValue2Offset;
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(TemplateObjectDescription);
|
||||
};
|
||||
|
||||
class TemplateMapShape final : public BaseShape<TemplateObjectDescription*> {
|
||||
public:
|
||||
static bool IsMatch(TemplateObjectDescription* key, Object* value);
|
||||
static uint32_t Hash(Isolate* isolate, TemplateObjectDescription* key);
|
||||
static uint32_t HashForObject(Isolate* isolate, Object* object);
|
||||
|
||||
static constexpr int kPrefixSize = 0;
|
||||
static constexpr int kEntrySize = 2;
|
||||
};
|
||||
|
||||
class TemplateMap final : public HashTable<TemplateMap, TemplateMapShape> {
|
||||
public:
|
||||
static Handle<TemplateMap> New(Isolate* isolate);
|
||||
|
||||
// Tries to lookup the given {key} in the {template_map}. Returns the
|
||||
// value if it's found, otherwise returns an empty MaybeHandle.
|
||||
WARN_UNUSED_RESULT static MaybeHandle<JSArray> Lookup(
|
||||
Handle<TemplateMap> template_map, Handle<TemplateObjectDescription> key);
|
||||
|
||||
// Adds the {key} / {value} pair to the {template_map} and returns the
|
||||
// new TemplateMap (we might need to re-allocate). This assumes that
|
||||
// there's no entry for {key} in the {template_map} already.
|
||||
static Handle<TemplateMap> Add(Handle<TemplateMap> template_map,
|
||||
Handle<TemplateObjectDescription> key,
|
||||
Handle<JSArray> value);
|
||||
|
||||
DECL_CAST(TemplateMap)
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(TemplateMap);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -3673,9 +3673,8 @@ Expression* Parser::CloseTemplateLiteral(TemplateLiteralState* state, int start,
|
||||
return expr;
|
||||
} else {
|
||||
// GetTemplateObject
|
||||
const int32_t hash = ComputeTemplateLiteralHash(lit);
|
||||
Expression* template_object =
|
||||
factory()->NewGetTemplateObject(cooked_strings, raw_strings, hash, pos);
|
||||
factory()->NewGetTemplateObject(cooked_strings, raw_strings, pos);
|
||||
|
||||
// Call TagFn
|
||||
ZoneList<Expression*>* call_args =
|
||||
@ -3688,51 +3687,6 @@ Expression* Parser::CloseTemplateLiteral(TemplateLiteralState* state, int start,
|
||||
|
||||
namespace {
|
||||
|
||||
// http://burtleburtle.net/bob/hash/integer.html
|
||||
uint32_t HalfAvalance(uint32_t a) {
|
||||
a = (a + 0x479AB41D) + (a << 8);
|
||||
a = (a ^ 0xE4AA10CE) ^ (a >> 5);
|
||||
a = (a + 0x9942F0A6) - (a << 14);
|
||||
a = (a ^ 0x5AEDD67D) ^ (a >> 3);
|
||||
a = (a + 0x17BEA992) + (a << 7);
|
||||
return a;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int32_t Parser::ComputeTemplateLiteralHash(const TemplateLiteral* lit) {
|
||||
const ZoneList<const AstRawString*>* raw_strings = lit->raw();
|
||||
int total = raw_strings->length();
|
||||
DCHECK_GT(total, 0);
|
||||
|
||||
uint32_t running_hash = 0;
|
||||
|
||||
for (int index = 0; index < total; ++index) {
|
||||
if (index) {
|
||||
running_hash = StringHasher::ComputeRunningHashOneByte(
|
||||
running_hash, "${}", 3);
|
||||
}
|
||||
|
||||
const AstRawString* raw_string = raw_strings->at(index);
|
||||
if (raw_string->is_one_byte()) {
|
||||
const char* data = reinterpret_cast<const char*>(raw_string->raw_data());
|
||||
running_hash = StringHasher::ComputeRunningHashOneByte(
|
||||
running_hash, data, raw_string->length());
|
||||
} else {
|
||||
const uc16* data = reinterpret_cast<const uc16*>(raw_string->raw_data());
|
||||
running_hash = StringHasher::ComputeRunningHash(running_hash, data,
|
||||
raw_string->length());
|
||||
}
|
||||
}
|
||||
|
||||
// Pass {running_hash} throught a decent 'half avalance' hash function
|
||||
// and take the most significant bits (in Smi range).
|
||||
return static_cast<int32_t>(HalfAvalance(running_hash)) >>
|
||||
(sizeof(int32_t) * CHAR_BIT - kSmiValueSize);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool OnlyLastArgIsSpread(ZoneList<Expression*>* args) {
|
||||
for (int i = 0; i < args->length() - 1; i++) {
|
||||
if (args->at(i)->IsSpread()) {
|
||||
|
@ -556,7 +556,6 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
|
||||
Expression* expression);
|
||||
Expression* CloseTemplateLiteral(TemplateLiteralState* state, int start,
|
||||
Expression* tag);
|
||||
int32_t ComputeTemplateLiteralHash(const TemplateLiteral* lit);
|
||||
|
||||
ZoneList<Expression*>* PrepareSpreadArguments(ZoneList<Expression*>* list);
|
||||
Expression* SpreadCall(Expression* function, ZoneList<Expression*>* args,
|
||||
|
@ -584,13 +584,12 @@ RUNTIME_FUNCTION(Runtime_CreateAsyncFromSyncIterator) {
|
||||
Handle<JSReceiver>::cast(sync_iterator), next);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GetTemplateObject) {
|
||||
RUNTIME_FUNCTION(Runtime_CreateTemplateObject) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(TemplateObjectDescription, description, 0);
|
||||
|
||||
return *TemplateObjectDescription::GetTemplateObject(
|
||||
description, isolate->native_context());
|
||||
return *TemplateObjectDescription::CreateTemplateObject(description);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ReportMessage) {
|
||||
|
@ -326,7 +326,7 @@ namespace internal {
|
||||
F(Typeof, 1, 1) \
|
||||
F(UnwindAndFindExceptionHandler, 0, 1) \
|
||||
F(AllowDynamicFunction, 1, 1) \
|
||||
F(GetTemplateObject, 1, 1) \
|
||||
F(CreateTemplateObject, 1, 1) \
|
||||
F(ReportMessage, 1, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_LITERALS(F) \
|
||||
|
@ -342,27 +342,30 @@ var obj = {
|
||||
var a = 1;
|
||||
var b = 2;
|
||||
|
||||
tag`head${a}tail`;
|
||||
tag`head${b}tail`;
|
||||
|
||||
// Call-sites are cached by ParseNode. Same tag call in a loop
|
||||
// means same template object
|
||||
for (var i = 0; i < 2; ++i) {
|
||||
tag`head${i == 0 ? a : b}tail`;
|
||||
}
|
||||
assertEquals(2, callSites.length);
|
||||
assertSame(callSites[0], callSites[1]);
|
||||
|
||||
eval("tag`head${a}tail`");
|
||||
assertEquals(3, callSites.length);
|
||||
assertSame(callSites[1], callSites[2]);
|
||||
|
||||
eval("tag`head${b}tail`");
|
||||
// Tag calls within eval() never have the same ParseNode as the same tag
|
||||
// call from a different eval() invocation.
|
||||
for (var i = 0; i < 2; ++i) {
|
||||
eval("tag`head${i == 0 ? a : b}tail`");
|
||||
}
|
||||
assertEquals(4, callSites.length);
|
||||
assertSame(callSites[2], callSites[3]);
|
||||
assertTrue(callSites[1] !== callSites[2]);
|
||||
assertTrue(callSites[2] !== callSites[3]);
|
||||
|
||||
(new Function("tag", "a", "b", "return tag`head${a}tail`;"))(tag, 1, 2);
|
||||
assertEquals(5, callSites.length);
|
||||
assertSame(callSites[3], callSites[4]);
|
||||
assertTrue(callSites[3] !== callSites[4]);
|
||||
|
||||
(new Function("tag", "a", "b", "return tag`head${b}tail`;"))(tag, 1, 2);
|
||||
assertEquals(6, callSites.length);
|
||||
assertSame(callSites[4], callSites[5]);
|
||||
assertTrue(callSites[4] !== callSites[5]);
|
||||
|
||||
callSites = [];
|
||||
|
||||
@ -374,17 +377,19 @@ var obj = {
|
||||
|
||||
callSites = [];
|
||||
|
||||
eval("tag`\\\r\n\\\n\\\r`");
|
||||
eval("tag`\\\r\n\\\n\\\r`");
|
||||
for (var i = 0; i < 2; ++i) {
|
||||
eval("tag`\\\r\n\\\n\\\r`");
|
||||
}
|
||||
assertEquals(2, callSites.length);
|
||||
assertSame(callSites[0], callSites[1]);
|
||||
assertTrue(callSites[0] !== callSites[1]);
|
||||
assertEquals("", callSites[0][0]);
|
||||
assertEquals("\\\n\\\n\\\n", callSites[0].raw[0]);
|
||||
|
||||
callSites = [];
|
||||
|
||||
tag`\uc548\ub155`;
|
||||
tag`\uc548\ub155`;
|
||||
for (var i = 0; i < 2; ++i) {
|
||||
tag`\uc548\ub155`;
|
||||
}
|
||||
assertEquals(2, callSites.length);
|
||||
assertSame(callSites[0], callSites[1]);
|
||||
assertEquals("안녕", callSites[0][0]);
|
||||
|
@ -292,6 +292,14 @@
|
||||
'language/statements/try/tco-finally': [SKIP],
|
||||
'language/statements/while/tco-body': [SKIP],
|
||||
|
||||
# https://github.com/tc39/test262/pull/972
|
||||
'language/expressions/tagged-template/cache-identical-source-new-function': [FAIL],
|
||||
'language/expressions/tagged-template/cache-identical-source': [FAIL],
|
||||
'language/expressions/tagged-template/cache-differing-expressions-eval': [FAIL],
|
||||
'language/expressions/tagged-template/cache-identical-source-eval': [FAIL],
|
||||
'language/expressions/tagged-template/cache-differing-expressions-new-function': [FAIL],
|
||||
'language/expressions/tagged-template/cache-differing-expressions': [FAIL],
|
||||
|
||||
# https://bugs.chromium.org/p/v8/issues/detail?id=5064
|
||||
'language/expressions/arrow-function/dflt-params-duplicates': [FAIL],
|
||||
'language/expressions/async-arrow-function/dflt-params-duplicates': [FAIL],
|
||||
|
@ -176,7 +176,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
|
||||
builder.CreateObjectLiteral(0, 0, 0, reg);
|
||||
|
||||
// Emit tagged template operations.
|
||||
builder.GetTemplateObject(0);
|
||||
builder.GetTemplateObject(0, 0);
|
||||
|
||||
// Call operations.
|
||||
builder.CallAnyReceiver(reg, reg_list, 1)
|
||||
|
Loading…
Reference in New Issue
Block a user