From f8023f7166ea1ecfdfb2ac493af57a8c4edf5cf5 Mon Sep 17 00:00:00 2001 From: Suraj Sharma Date: Mon, 24 Sep 2018 11:32:28 -0700 Subject: [PATCH] Implemented StringTemplate Caching based on location in source Code. Changed the earlier implementation of caching the StringTemplates based on Raw String Literals to their location in the source code. --- lib/Runtime/ByteCode/ByteCodeEmitter.cpp | 19 +- lib/Runtime/ByteCode/ByteCodeSerializer.cpp | 11 +- lib/Runtime/ByteCode/FuncInfo.h | 2 +- lib/Runtime/Library/JavascriptLibrary.cpp | 343 +------------------- lib/Runtime/Library/JavascriptLibrary.h | 44 +-- test/es6/ES6StringTemplate.js | 111 +++---- 6 files changed, 58 insertions(+), 472 deletions(-) diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index a4f9055e3d1..11e02f181df 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -5692,20 +5692,13 @@ void ByteCodeGenerator::RecordAllStringTemplateCallsiteConstants(FuncInfo* funcI funcInfo->stringTemplateCallsiteRegisterMap.Map([byteCodeFunction](ParseNodePtr pnode, Js::RegSlot location) { Js::ScriptContext* scriptContext = byteCodeFunction->GetScriptContext(); - Js::JavascriptLibrary* library = scriptContext->GetLibrary(); - Js::RecyclableObject* callsiteObject = library->TryGetStringTemplateCallsiteObject(pnode); - - if (callsiteObject == nullptr) - { - Js::RecyclableObject* rawArray = ByteCodeGenerator::BuildArrayFromStringList(pnode->AsParseNodeStrTemplate()->pnodeStringRawLiterals, pnode->AsParseNodeStrTemplate()->countStringLiterals, scriptContext); - rawArray->Freeze(); - - callsiteObject = ByteCodeGenerator::BuildArrayFromStringList(pnode->AsParseNodeStrTemplate()->pnodeStringLiterals, pnode->AsParseNodeStrTemplate()->countStringLiterals, scriptContext); - callsiteObject->SetPropertyWithAttributes(Js::PropertyIds::raw, rawArray, PropertyNone, nullptr); - callsiteObject->Freeze(); + + Js::RecyclableObject* rawArray = ByteCodeGenerator::BuildArrayFromStringList(pnode->AsParseNodeStrTemplate()->pnodeStringRawLiterals, pnode->AsParseNodeStrTemplate()->countStringLiterals, scriptContext); + rawArray->Freeze(); - library->AddStringTemplateCallsiteObject(callsiteObject); - } + Js::RecyclableObject* callsiteObject = ByteCodeGenerator::BuildArrayFromStringList(pnode->AsParseNodeStrTemplate()->pnodeStringLiterals, pnode->AsParseNodeStrTemplate()->countStringLiterals, scriptContext); + callsiteObject->SetPropertyWithAttributes(Js::PropertyIds::raw, rawArray, PropertyNone, nullptr); + callsiteObject->Freeze(); byteCodeFunction->RecordConstant(byteCodeFunction->MapRegSlot(location), callsiteObject); }); diff --git a/lib/Runtime/ByteCode/ByteCodeSerializer.cpp b/lib/Runtime/ByteCode/ByteCodeSerializer.cpp index d8f715b4341..eaef1ae29b2 100644 --- a/lib/Runtime/ByteCode/ByteCodeSerializer.cpp +++ b/lib/Runtime/ByteCode/ByteCodeSerializer.cpp @@ -3210,16 +3210,7 @@ class ByteCodeBufferReader callsite->SetPropertyWithAttributes(Js::PropertyIds::raw, rawArray, PropertyNone, nullptr); callsite->Freeze(); - JavascriptLibrary* library = scriptContext->GetLibrary(); - - var = library->TryGetStringTemplateCallsiteObject(callsite); - - if (var == nullptr) - { - library->AddStringTemplateCallsiteObject(callsite); - var = callsite; - } - + var = callsite; LEAVE_PINNED_SCOPE(); return current; diff --git a/lib/Runtime/ByteCode/FuncInfo.h b/lib/Runtime/ByteCode/FuncInfo.h index ec8b7424328..99686b9735a 100644 --- a/lib/Runtime/ByteCode/FuncInfo.h +++ b/lib/Runtime/ByteCode/FuncInfo.h @@ -146,7 +146,7 @@ class FuncInfo typedef JsUtil::BaseDictionary DoubleRegisterMap; DoubleRegisterMap doubleConstantToRegister; // maps double constant to register - typedef JsUtil::BaseDictionary StringTemplateCallsiteRegisterMap; + typedef JsUtil::BaseDictionary StringTemplateCallsiteRegisterMap; StringTemplateCallsiteRegisterMap stringTemplateCallsiteRegisterMap; // maps string template callsite constant to register Scope *paramScope; // top level scope for parameter default values diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp index 0b7ef453d27..fadae5b2590 100644 --- a/lib/Runtime/Library/JavascriptLibrary.cpp +++ b/lib/Runtime/Library/JavascriptLibrary.cpp @@ -5253,63 +5253,7 @@ namespace Js #endif return function; } - - void JavascriptLibrary::EnsureStringTemplateCallsiteObjectList() - { - if (this->stringTemplateCallsiteObjectList == nullptr) - { - this->stringTemplateCallsiteObjectList = RecyclerNew(GetRecycler(), StringTemplateCallsiteObjectList, GetRecycler()); - } - } - - void JavascriptLibrary::AddStringTemplateCallsiteObject(RecyclableObject* callsite) - { - this->EnsureStringTemplateCallsiteObjectList(); - - RecyclerWeakReference* callsiteRef = this->GetRecycler()->CreateWeakReferenceHandle(callsite); - - this->stringTemplateCallsiteObjectList->Item(callsiteRef); - } - - RecyclableObject* JavascriptLibrary::TryGetStringTemplateCallsiteObject(ParseNodePtr pnode) - { - this->EnsureStringTemplateCallsiteObjectList(); - - RecyclerWeakReference* callsiteRef = this->stringTemplateCallsiteObjectList->LookupWithKey(pnode); - - if (callsiteRef) - { - RecyclableObject* callsite = callsiteRef->Get(); - - if (callsite) - { - return callsite; - } - } - - return nullptr; - } - - RecyclableObject* JavascriptLibrary::TryGetStringTemplateCallsiteObject(RecyclableObject* callsite) - { - this->EnsureStringTemplateCallsiteObjectList(); - - RecyclerWeakReference* callsiteRef = this->GetRecycler()->CreateWeakReferenceHandle(callsite); - RecyclerWeakReference* existingCallsiteRef = this->stringTemplateCallsiteObjectList->LookupWithKey(callsiteRef); - - if (existingCallsiteRef) - { - RecyclableObject* existingCallsite = existingCallsiteRef->Get(); - - if (existingCallsite) - { - return existingCallsite; - } - } - - return nullptr; - } - + #if DBG_DUMP const char16* JavascriptLibrary::GetStringTemplateCallsiteObjectKey(Var callsite) { @@ -5374,290 +5318,7 @@ namespace Js } #endif - bool StringTemplateCallsiteObjectComparer::Equals(ParseNodePtr x, RecyclerWeakReference* y) - { - Assert(x != nullptr); - Assert(x->nop == knopStrTemplate); - - Js::RecyclableObject* obj = y->Get(); - - // If the weak reference is dead, we can't be equal. - if (obj == nullptr) - { - return false; - } - - Js::ES5Array* callsite = Js::ES5Array::FromVar(obj); - uint32 length = callsite->GetLength(); - Js::Var element; - Js::JavascriptString* str; - IdentPtr pid; - - // If the length of string literals is different, these callsite objects are not equal. - if (x->AsParseNodeStrTemplate()->countStringLiterals != length) - { - return false; - } - - JS_REENTRANCY_LOCK(reentrancyLock, callsite->GetScriptContext()->GetThreadContext()); - Unused(reentrancyLock); - - element = Js::JavascriptOperators::OP_GetProperty(callsite, Js::PropertyIds::raw, callsite->GetScriptContext()); - Js::ES5Array* rawArray = Js::ES5Array::FromVar(element); - - // Length of the raw strings should be the same as the cooked string literals. - AssertOrFailFast(length != 0 && length == rawArray->GetLength()); - - x = x->AsParseNodeStrTemplate()->pnodeStringRawLiterals; - - for (uint32 i = 0; i < length - 1; i++) - { - BOOL hasElem = rawArray->DirectGetItemAt(i, &element); - AssertOrFailFast(hasElem); - str = Js::JavascriptString::FromVar(element); - - Assert(x->nop == knopList); - Assert(x->AsParseNodeBin()->pnode1->nop == knopStr); - - pid = x->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid; - - // If strings have different length, they aren't equal - if (pid->Cch() != str->GetLength()) - { - return false; - } - - // If the strings at this index are not equal, the callsite objects are not equal. - if (!JsUtil::CharacterBuffer::StaticEquals(pid->Psz(), str->GetString(), str->GetLength())) - { - return false; - } - - x = x->AsParseNodeBin()->pnode2; - } - - // There should be one more string in the callsite array - and the final string in the ParseNode - - BOOL hasLastElem = rawArray->DirectGetItemAt(length - 1, &element); - AssertOrFailFast(hasLastElem); - str = Js::JavascriptString::FromVar(element); - - Assert(x->nop == knopStr); - pid = x->AsParseNodeStr()->pid; - - // If strings have different length, they aren't equal - if (pid->Cch() != str->GetLength()) - { - return false; - } - - // If the strings at this index are not equal, the callsite objects are not equal. - if (!JsUtil::CharacterBuffer::StaticEquals(pid->Psz(), str->GetString(), str->GetLength())) - { - return false; - } - - return true; - } - - bool StringTemplateCallsiteObjectComparer::Equals(ParseNodePtr x, ParseNodePtr y) - { - Assert(x != nullptr && y != nullptr); - Assert(x->nop == knopStrTemplate && y->nop == knopStrTemplate); - - // If the ParseNode is the same, they are equal. - if (x == y) - { - return true; - } - - x = x->AsParseNodeStrTemplate()->pnodeStringRawLiterals; - y = y->AsParseNodeStrTemplate()->pnodeStringRawLiterals; - - // If one of the templates only includes one string value, the raw literals ParseNode will - // be a knopStr instead of knopList. - if (x->nop != y->nop) - { - return false; - } - - const char16* pid_x; - const char16* pid_y; - - while (x->nop == knopList) - { - // If y is knopStr here, that means x has more strings in the list than y does. - if (y->nop != knopList) - { - return false; - } - - Assert(x->AsParseNodeBin()->pnode1->nop == knopStr); - Assert(y->AsParseNodeBin()->pnode1->nop == knopStr); - - pid_x = x->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->Psz(); - pid_y = y->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->Psz(); - - // If the pid values of each raw string don't match each other, these are different. - if (!DefaultComparer::Equals(pid_x, pid_y)) - { - return false; - } - - x = x->AsParseNodeBin()->pnode2; - y = y->AsParseNodeBin()->pnode2; - } - - // If y is still knopList here, that means y has more strings in the list than x does. - if (y->nop != knopStr) - { - return false; - } - - Assert(x->nop == knopStr); - - pid_x = x->AsParseNodeStr()->pid->Psz(); - pid_y = y->AsParseNodeStr()->pid->Psz(); - - // This is the final string in the raw literals list. Return true if they are equal. - return DefaultComparer::Equals(pid_x, pid_y); - } - - hash_t StringTemplateCallsiteObjectComparer::GetHashCode(ParseNodePtr i) - { - hash_t hash = 0; - - Assert(i != nullptr); - Assert(i->nop == knopStrTemplate); - - i = i->AsParseNodeStrTemplate()->pnodeStringRawLiterals; - - const char16* pid; - - while (i->nop == knopList) - { - Assert(i->AsParseNodeBin()->pnode1->nop == knopStr); - - pid = i->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->Psz(); - - hash ^= DefaultComparer::GetHashCode(pid); - hash ^= DefaultComparer::GetHashCode(_u("${}")); - - i = i->AsParseNodeBin()->pnode2; - } - - Assert(i->nop == knopStr); - - pid = i->AsParseNodeStr()->pid->Psz(); - - hash ^= DefaultComparer::GetHashCode(pid); - - return hash; - } - - bool StringTemplateCallsiteObjectComparer*>::Equals(RecyclerWeakReference* x, ParseNodePtr y) - { - return StringTemplateCallsiteObjectComparer::Equals(y, x); - } - - bool StringTemplateCallsiteObjectComparer*>::Equals(RecyclerWeakReference* x, RecyclerWeakReference* y) - { - Js::RecyclableObject* objLeft = x->Get(); - Js::RecyclableObject* objRight = y->Get(); - - // If either WeakReference is dead, we can't be equal to anything. - if (objLeft == nullptr || objRight == nullptr) - { - return false; - } - - // If the Var pointers are the same, they are equal. - if (objLeft == objRight) - { - return true; - } - - Js::ES5Array* arrayLeft = Js::ES5Array::FromVar(objLeft); - Js::ES5Array* arrayRight = Js::ES5Array::FromVar(objRight); - uint32 lengthLeft = arrayLeft->GetLength(); - uint32 lengthRight = arrayRight->GetLength(); - Js::Var varLeft; - Js::Var varRight; - - // If the length of string literals is different, these callsite objects are not equal. - if (lengthLeft != lengthRight) - { - return false; - } - - AssertOrFailFast(lengthLeft != 0 && lengthRight != 0); - - JS_REENTRANCY_LOCK(reentrancyLock, arrayLeft->GetScriptContext()->GetThreadContext()); - Unused(reentrancyLock); - - // Change to the set of raw strings. - varLeft = Js::JavascriptOperators::OP_GetProperty(arrayLeft, Js::PropertyIds::raw, arrayLeft->GetScriptContext()); - arrayLeft = Js::ES5Array::FromVar(varLeft); - - varRight = Js::JavascriptOperators::OP_GetProperty(arrayRight, Js::PropertyIds::raw, arrayRight->GetScriptContext()); - arrayRight = Js::ES5Array::FromVar(varRight); - - // Length of the raw strings should be the same as the cooked string literals. - AssertOrFailFast(lengthLeft == arrayLeft->GetLength()); - AssertOrFailFast(lengthRight == arrayRight->GetLength()); - - for (uint32 i = 0; i < lengthLeft; i++) - { - BOOL hasLeft = arrayLeft->DirectGetItemAt(i, &varLeft); - AssertOrFailFast(hasLeft); - BOOL hasRight = arrayRight->DirectGetItemAt(i, &varRight); - AssertOrFailFast(hasRight); - - // If the strings at this index are not equal, the callsite objects are not equal. - if (!Js::JavascriptString::Equals(JavascriptString::FromVar(varLeft), JavascriptString::FromVar(varRight))) - { - return false; - } - } - - return true; - } - - hash_t StringTemplateCallsiteObjectComparer*>::GetHashCode(RecyclerWeakReference* o) - { - hash_t hash = 0; - - Js::RecyclableObject* obj = o->Get(); - - if (obj == nullptr) - { - return hash; - } - JS_REENTRANCY_LOCK(reentrancyLock, obj->GetScriptContext()->GetThreadContext()); - Unused(reentrancyLock); - - Js::ES5Array* callsite = Js::ES5Array::FromVar(obj); - Js::Var var = Js::JavascriptOperators::OP_GetProperty(callsite, Js::PropertyIds::raw, callsite->GetScriptContext()); - Js::ES5Array* rawArray = Js::ES5Array::FromVar(var); - - AssertOrFailFast(rawArray->GetLength() > 0); - - rawArray->DirectGetItemAt(0, &var); - Js::JavascriptString* str = Js::JavascriptString::FromVar(var); - hash ^= DefaultComparer::GetHashCode(str->GetSz()); - - for (uint32 i = 1; i < rawArray->GetLength(); i++) - { - hash ^= DefaultComparer::GetHashCode(_u("${}")); - - BOOL hasItem = rawArray->DirectGetItemAt(i, &var); - AssertOrFailFast(hasItem); - str = Js::JavascriptString::FromVar(var); - hash ^= DefaultComparer::GetHashCode(str->GetSz()); - } - - return hash; - } + DynamicType * JavascriptLibrary::GetObjectLiteralType(uint16 requestedInlineSlotCapacity) { diff --git a/lib/Runtime/Library/JavascriptLibrary.h b/lib/Runtime/Library/JavascriptLibrary.h index 72b5e2914dd..e9f9bba9777 100644 --- a/lib/Runtime/Library/JavascriptLibrary.h +++ b/lib/Runtime/Library/JavascriptLibrary.h @@ -158,35 +158,6 @@ namespace Js }; #endif - template - struct StringTemplateCallsiteObjectComparer - { - static bool Equals(T x, T y) - { - static_assert(false, "Unexpected type T"); - } - static hash_t GetHashCode(T i) - { - static_assert(false, "Unexpected type T"); - } - }; - - template <> - struct StringTemplateCallsiteObjectComparer - { - static bool Equals(ParseNodePtr x, RecyclerWeakReference* y); - static bool Equals(ParseNodePtr x, ParseNodePtr y); - static hash_t GetHashCode(ParseNodePtr i); - }; - - template <> - struct StringTemplateCallsiteObjectComparer*> - { - static bool Equals(RecyclerWeakReference* x, RecyclerWeakReference* y); - static bool Equals(RecyclerWeakReference* x, ParseNodePtr y); - static hash_t GetHashCode(RecyclerWeakReference* o); - }; - class JavascriptLibrary : public JavascriptLibraryBase { friend class EditAndContinue; @@ -475,13 +446,6 @@ namespace Js Field(JsrtExternalTypesCache*) jsrtExternalTypesCache; Field(FunctionBody*) fakeGlobalFuncForUndefer; - typedef JsUtil::BaseHashSet*, Recycler, PowerOf2SizePolicy, RecyclerWeakReference*, StringTemplateCallsiteObjectComparer> StringTemplateCallsiteObjectList; - - // Used to store a list of template callsite objects. - // We use the raw strings in the callsite object (or a string template parse node) to identify unique callsite objects in the list. - // See abstract operation GetTemplateObject in ES6 Spec (RC1) 12.2.8.3 - Field(StringTemplateCallsiteObjectList*) stringTemplateCallsiteObjectList; - Field(ModuleRecordList*) moduleRecordList; Field(OnlyWritablePropertyProtoChainCache) typesWithOnlyWritablePropertyProtoChain; @@ -558,7 +522,6 @@ namespace Js cacheForCopyOnAccessArraySegments(nullptr), #endif referencedPropertyRecords(nullptr), - stringTemplateCallsiteObjectList(nullptr), moduleRecordList(nullptr), rootPath(nullptr), bindRefChunkBegin(nullptr), @@ -1061,12 +1024,7 @@ namespace Js static bool IsCachedCopyOnAccessArrayCallSite(const JavascriptLibrary *lib, ArrayCallSiteInfo *arrayInfo); template static void CheckAndConvertCopyOnAccessNativeIntArray(const T instance); -#endif - - void EnsureStringTemplateCallsiteObjectList(); - void AddStringTemplateCallsiteObject(RecyclableObject* callsite); - RecyclableObject* TryGetStringTemplateCallsiteObject(ParseNodePtr pnode); - RecyclableObject* TryGetStringTemplateCallsiteObject(RecyclableObject* callsite); +#endif static void CheckAndInvalidateIsConcatSpreadableCache(PropertyId propertyId, ScriptContext *scriptContext); diff --git a/test/es6/ES6StringTemplate.js b/test/es6/ES6StringTemplate.js index aaaea5e08a1..1dc045f24c1 100644 --- a/test/es6/ES6StringTemplate.js +++ b/test/es6/ES6StringTemplate.js @@ -127,7 +127,7 @@ var tests = [ var callsite3 = GetCallsite`simple template literal 3`; var callsite4 = GetCallsite`simple template literal 3`; - assert.isTrue(callsite3 === callsite4, "different string template literals with the same string literal value create identical callsite objects"); + assert.isFalse(callsite3 === callsite4, "different string template literals (with the same string literal value) create different callsite objects"); var loopCallsite = undefined; for (var i = 0; i < 10; i++) { @@ -349,65 +349,57 @@ var tests = [ } }, { - name: "String template callsite objects are unique per-Realm and indexed by the raw strings", + name: "Each string template literal corresponds to exactly one (cached) callsite object", body: function() { - var callsite = undefined; - var counter = 0; - - function tag(c) - { - counter++; - - assert.areEqual('uniquestringforrealmcachetest\\n', c.raw[0], 'String template callsite has correct raw value'); - - if (callsite === undefined) { - callsite = c; + var callsite1 = GetCallsite`simple template literal 1`; + var callsite2 = GetCallsite`simple template literal 2`; + + assert.isFalse(callsite1 === callsite2, "different string template literals create different callsite objects"); + + var callsite3 = GetCallsite`simple template literal 3`; + var callsite4 = GetCallsite`simple template literal 3`; + + assert.isFalse(callsite3 === callsite4, "different string template literals (with the same string literal value) create different callsite objects"); + + var loopCallsite = undefined; + for (var i = 0; i < 10; i++) { + var c = GetCallsite`loop template literal ${i}`; + + if (loopCallsite === undefined) { + loopCallsite = c; } else { - assert.isTrue(c === callsite, 'Callsite is correctly cached per-Realm'); + assert.isTrue(loopCallsite === c, "string template literal used in a loop reuses the same callsite object."); } + + assert.areEqual(2, c.length, "loop callsite has expected count of string literals"); + assert.areEqual("loop template literal ", c[0], "loop callsite has expected first string literal value"); + assert.areEqual("", c[1], "loop callsite has expected second string literal value"); + + assert.areEqual(2, c.raw.length, "loop callsite.raw has expected count of string literals"); + assert.areEqual("loop template literal ", c.raw[0], "loop callsite.raw has expected first string literal value"); + assert.areEqual("", c.raw[1], "loop callsite.raw has expected second string literal value"); } - - function foo() { - tag`uniquestringforrealmcachetest\n`; - tag`uniquestringforrealmcachetest\n`; - } - - foo(); - foo(); - - function foo2() { - tag`uniquestringforrealmcachetest\n`; - tag`uniquestringforrealmcachetest\n`; - } - - foo2(); - foo2(); - - function foo3() { - eval('tag`uniquestringforrealmcachetest\\n`'); - eval('tag`uniquestringforrealmcachetest\\n`'); + + loopCallsite = undefined + for (var i = 0; i < 10; i++) { + var c = GetExpectedCachedCallsite(); + + if (loopCallsite === undefined) { + loopCallsite = c; + } else { + assert.isTrue(loopCallsite === c, "string template declared in other function returns same callsite object when function called."); + } + + assert.areEqual(3, c.length, "loop callsite has expected count of string literals"); + assert.areEqual("some string template ", c[0], "loop callsite has expected first string literal value"); + assert.areEqual(" with replacements ", c[1], "loop callsite has expected second string literal value"); + assert.areEqual("", c[2], "loop callsite has expected third string literal value"); + + assert.areEqual(3, c.raw.length, "loop callsite.raw has expected count of string literals"); + assert.areEqual("some string template ", c.raw[0], "loop callsite.raw has expected first string literal value"); + assert.areEqual(" with replacements ", c.raw[1], "loop callsite.raw has expected second string literal value"); + assert.areEqual("", c.raw[2], "loop callsite.raw has expected third string literal value"); } - - foo3(); - foo3(); - - counter = 0; - - var foo4 = new Function('t','t`uniquestringforrealmcachetest\\n`;'); - - foo4(tag); - foo4(tag); - - assert.areEqual(2, counter, "tag function is called correct number of times"); - - counter = 0; - - var foo5 = new Function('t','eval("t`uniquestringforrealmcachetest\\\\n`;");'); - - foo5(tag); - foo5(tag); - - assert.areEqual(2, counter, "tag function is called correct number of times"); } }, { @@ -426,15 +418,6 @@ after`; assert.isFalse(callsite1 === callsite2, 'Callsite objects are not the same '); } }, - { - name: "Callsite objects are constant even if replacement values differ", - body: function() { - var callsite1 = GetCallsite`string1${'r1'}string2${'r2'}string3`; - var callsite2 = GetCallsite`string1${'r3'}string2${'r4'}string3`; - - assert.isTrue(callsite1 === callsite2, "Callsite objects are strictly equal"); - } - }, { name: "Octal escape sequences are not allowed in string template literals", body: function() {