diff --git a/lib/Backend/GlobOptFields.cpp b/lib/Backend/GlobOptFields.cpp index dca85bd2119..216c67439fd 100644 --- a/lib/Backend/GlobOptFields.cpp +++ b/lib/Backend/GlobOptFields.cpp @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "Backend.h" @@ -533,17 +534,17 @@ GlobOpt::ProcessFieldKills(IR::Instr *instr, BVSparse *bv, bo break; case IR::JnHelperMethod::HelperRegExp_Exec: - case IR::JnHelperMethod::HelperRegExp_ExecResultNotUsed: - case IR::JnHelperMethod::HelperRegExp_ExecResultUsed: - case IR::JnHelperMethod::HelperRegExp_ExecResultUsedAndMayBeTemp: - case IR::JnHelperMethod::HelperRegExp_MatchResultNotUsed: - case IR::JnHelperMethod::HelperRegExp_MatchResultUsed: - case IR::JnHelperMethod::HelperRegExp_MatchResultUsedAndMayBeTemp: - case IR::JnHelperMethod::HelperRegExp_ReplaceStringResultUsed: - case IR::JnHelperMethod::HelperRegExp_ReplaceStringResultNotUsed: - case IR::JnHelperMethod::HelperRegExp_SplitResultNotUsed: - case IR::JnHelperMethod::HelperRegExp_SplitResultUsed: - case IR::JnHelperMethod::HelperRegExp_SplitResultUsedAndMayBeTemp: + // case IR::JnHelperMethod::HelperRegExp_ExecResultNotUsed: + // case IR::JnHelperMethod::HelperRegExp_ExecResultUsed: + // case IR::JnHelperMethod::HelperRegExp_ExecResultUsedAndMayBeTemp: + // case IR::JnHelperMethod::HelperRegExp_MatchResultNotUsed: + // case IR::JnHelperMethod::HelperRegExp_MatchResultUsed: + // case IR::JnHelperMethod::HelperRegExp_MatchResultUsedAndMayBeTemp: + // case IR::JnHelperMethod::HelperRegExp_ReplaceStringResultUsed: + // case IR::JnHelperMethod::HelperRegExp_ReplaceStringResultNotUsed: + // case IR::JnHelperMethod::HelperRegExp_SplitResultNotUsed: + // case IR::JnHelperMethod::HelperRegExp_SplitResultUsed: + // case IR::JnHelperMethod::HelperRegExp_SplitResultUsedAndMayBeTemp: case IR::JnHelperMethod::HelperRegExp_SymbolSearch: case IR::JnHelperMethod::HelperString_Match: case IR::JnHelperMethod::HelperString_Search: diff --git a/lib/Backend/JnHelperMethodList.h b/lib/Backend/JnHelperMethodList.h index e8fcbd397d4..c5ea7af4a22 100644 --- a/lib/Backend/JnHelperMethodList.h +++ b/lib/Backend/JnHelperMethodList.h @@ -526,18 +526,19 @@ HELPERCALL(String_PadEnd, Js::JavascriptString::EntryPadEnd, 0) HELPERCALLCHK(GlobalObject_ParseInt, Js::GlobalObject::EntryParseInt, 0) HELPERCALLCHK(Object_HasOwnProperty, Js::JavascriptObject::EntryHasOwnProperty, 0) -HELPERCALL(RegExp_SplitResultUsed, Js::RegexHelper::RegexSplitResultUsed, 0) -HELPERCALL(RegExp_SplitResultUsedAndMayBeTemp, Js::RegexHelper::RegexSplitResultUsedAndMayBeTemp, 0) -HELPERCALL(RegExp_SplitResultNotUsed, Js::RegexHelper::RegexSplitResultNotUsed, 0) -HELPERCALL(RegExp_MatchResultUsed, Js::RegexHelper::RegexMatchResultUsed, 0) -HELPERCALL(RegExp_MatchResultUsedAndMayBeTemp, Js::RegexHelper::RegexMatchResultUsedAndMayBeTemp, 0) -HELPERCALL(RegExp_MatchResultNotUsed, Js::RegexHelper::RegexMatchResultNotUsed, 0) +// TODO: Cleanup +// HELPERCALL(RegExp_SplitResultUsed, Js::RegexHelper::RegexSplitResultUsed, 0) +// HELPERCALL(RegExp_SplitResultUsedAndMayBeTemp, Js::RegexHelper::RegexSplitResultUsedAndMayBeTemp, 0) +// HELPERCALL(RegExp_SplitResultNotUsed, Js::RegexHelper::RegexSplitResultNotUsed, 0) +// HELPERCALL(RegExp_MatchResultUsed, Js::RegexHelper::RegexMatchResultUsed, 0) +// HELPERCALL(RegExp_MatchResultUsedAndMayBeTemp, Js::RegexHelper::RegexMatchResultUsedAndMayBeTemp, 0) +// HELPERCALL(RegExp_MatchResultNotUsed, Js::RegexHelper::RegexMatchResultNotUsed, 0) HELPERCALL(RegExp_Exec, Js::JavascriptRegExp::EntryExec, AttrTempObjectProducing) -HELPERCALL(RegExp_ExecResultUsed, Js::RegexHelper::RegexExecResultUsed, 0) -HELPERCALL(RegExp_ExecResultUsedAndMayBeTemp, Js::RegexHelper::RegexExecResultUsedAndMayBeTemp, 0) -HELPERCALL(RegExp_ExecResultNotUsed, Js::RegexHelper::RegexExecResultNotUsed, 0) -HELPERCALL(RegExp_ReplaceStringResultUsed, Js::RegexHelper::RegexReplaceResultUsed, 0) -HELPERCALL(RegExp_ReplaceStringResultNotUsed, Js::RegexHelper::RegexReplaceResultNotUsed, 0) +// HELPERCALL(RegExp_ExecResultUsed, Js::RegexHelper::RegexExecResultUsed, 0) +// HELPERCALL(RegExp_ExecResultUsedAndMayBeTemp, Js::RegexHelper::RegexExecResultUsedAndMayBeTemp, 0) +// HELPERCALL(RegExp_ExecResultNotUsed, Js::RegexHelper::RegexExecResultNotUsed, 0) +// HELPERCALL(RegExp_ReplaceStringResultUsed, Js::RegexHelper::RegexReplaceResultUsed, 0) +// HELPERCALL(RegExp_ReplaceStringResultNotUsed, Js::RegexHelper::RegexReplaceResultNotUsed, 0) HELPERCALL(RegExp_SymbolSearch, Js::JavascriptRegExp::EntrySymbolSearch, 0) HELPERCALL(Uint8ClampedArraySetItem, (BOOL (*)(Js::Uint8ClampedArray * arr, uint32 index, Js::Var value))&Js::Uint8ClampedArray::DirectSetItem, AttrCanNotBeReentrant) diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index 7a0b5b00b56..b4dff1adc40 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- -// Copyright (C) Microsoft Corporation and contributors. All rights reserved. +// Copyright (C) Microsoft. All rights reserved. +// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "Backend.h" @@ -770,13 +771,14 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa Assert(src1->IsHelperCallOpnd()); switch (src1->AsHelperCallOpnd()->m_fnHelper) { - case IR::JnHelperMethod::HelperString_Split: - case IR::JnHelperMethod::HelperString_Match: - GenerateFastInlineStringSplitMatch(instr); - break; - case IR::JnHelperMethod::HelperRegExp_Exec: - GenerateFastInlineRegExpExec(instr); - break; + // Cause spec deviations + // case IR::JnHelperMethod::HelperString_Split: + // case IR::JnHelperMethod::HelperString_Match: + // GenerateFastInlineStringSplitMatch(instr); + // break; + // case IR::JnHelperMethod::HelperRegExp_Exec: + // GenerateFastInlineRegExpExec(instr); + // break; case IR::JnHelperMethod::HelperGlobalObject_ParseInt: GenerateFastInlineGlobalObjectParseInt(instr); break; @@ -20378,6 +20380,7 @@ Lowerer::GenerateFastInlineHasOwnProperty(IR::Instr * instr) RelocateCallDirectToHelperPath(tmpInstr, labelHelper); } +// TODO: Cleanup bool Lowerer::ShouldGenerateStringReplaceFastPath(IR::Instr * callInstr, IntConstType argCount) { @@ -20390,462 +20393,464 @@ Lowerer::ShouldGenerateStringReplaceFastPath(IR::Instr * callInstr, IntConstType // arg3(s14)<8>.var = ArgOut_A s4.var, arg2(s13)<4>.var #001c <---- c // s0[LikelyString].var = CallI s5[ffunc].var, arg3(s14)<8>.var #0020 - IR::Opnd *linkOpnd = callInstr->GetSrc2(); - Assert(argCount == 2); - - while(linkOpnd->IsSymOpnd()) - { - IR::SymOpnd *src2 = linkOpnd->AsSymOpnd(); - StackSym *sym = src2->m_sym->AsStackSym(); - Assert(sym->m_isSingleDef); - IR::Instr *argInstr = sym->m_instrDef; + return false; + // IR::Opnd *linkOpnd = callInstr->GetSrc2(); + // Assert(argCount == 2); - Assert(argCount >= 0); - // check to see if 'a' and 'c' are likely strings - if((argCount == 2 || argCount == 0) && (!argInstr->GetSrc1()->GetValueType().IsLikelyString())) - { - return false; - } - // we want 'b' to be regex. Don't generate fastpath if it is a tagged int - if((argCount == 1) && (argInstr->GetSrc1()->IsTaggedInt())) - { - return false; - } - argCount--; - linkOpnd = argInstr->GetSrc2(); - } - return true; + // while(linkOpnd->IsSymOpnd()) + // { + // IR::SymOpnd *src2 = linkOpnd->AsSymOpnd(); + // StackSym *sym = src2->m_sym->AsStackSym(); + // Assert(sym->m_isSingleDef); + // IR::Instr *argInstr = sym->m_instrDef; + + // Assert(argCount >= 0); + // // check to see if 'a' and 'c' are likely strings + // if((argCount == 2 || argCount == 0) && (!argInstr->GetSrc1()->GetValueType().IsLikelyString())) + // { + // return false; + // } + // // we want 'b' to be regex. Don't generate fastpath if it is a tagged int + // if((argCount == 1) && (argInstr->GetSrc1()->IsTaggedInt())) + // { + // return false; + // } + // argCount--; + // linkOpnd = argInstr->GetSrc2(); + // } + // return true; } bool Lowerer::GenerateFastReplace(IR::Opnd* strOpnd, IR::Opnd* src1, IR::Opnd* src2, IR::Instr *callInstr, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel) { - // a.replace(b,c) - // We want to emit the fast path if 'a' and 'c' are strings and 'b' is a regex - // - // strOpnd --> a - // src1 --> b - // src2 --> c - - IR::Opnd * callDst = callInstr->GetDst(); - - Assert(strOpnd->GetValueType().IsLikelyString() && src2->GetValueType().IsLikelyString()); - - if(!strOpnd->GetValueType().IsString()) - { - strOpnd = GetRegOpnd(strOpnd, insertInstr, m_func, TyVar); - - this->GenerateStringTest(strOpnd->AsRegOpnd(), insertInstr, labelHelper); - } - - if(!src1->IsNotTaggedValue()) - { - m_lowererMD.GenerateObjectTest(src1, insertInstr, labelHelper); - } - - IR::Opnd * vtableOpnd = LoadVTableValueOpnd(insertInstr, VTableValue::VtableJavascriptRegExp); - - // cmp [regex], vtableAddress - // jne $labelHelper - src1 = GetRegOpnd(src1, insertInstr, m_func, TyVar); - InsertCompareBranch( - IR::IndirOpnd::New(src1->AsRegOpnd(), 0, TyMachPtr, insertInstr->m_func), - vtableOpnd, - Js::OpCode::BrNeq_A, - labelHelper, - insertInstr); - - if(!src2->GetValueType().IsString()) - { - src2 = GetRegOpnd(src2, insertInstr, m_func, TyVar); - this->GenerateStringTest(src2->AsRegOpnd(), insertInstr, labelHelper); - } - - IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, insertInstr->m_func); - if (callDst) - { - helperCallInstr->SetDst(callDst); - } - insertInstr->InsertBefore(helperCallInstr); - - if (insertInstr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(insertInstr->GetBailOutKind())) - { - helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, insertInstr->GetBailOutInfo(), insertInstr->GetBailOutKind(), insertInstr); - } - - //scriptContext, pRegEx, pThis, pReplace (to be pushed in reverse order) - - // pReplace, pThis, pRegEx - this->m_lowererMD.LoadHelperArgument(helperCallInstr, src2); - this->m_lowererMD.LoadHelperArgument(helperCallInstr, strOpnd); - this->m_lowererMD.LoadHelperArgument(helperCallInstr, src1); - - // script context - LoadScriptContext(helperCallInstr); - - if(callDst) - { - m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperRegExp_ReplaceStringResultUsed); - } - else - { - m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperRegExp_ReplaceStringResultNotUsed); - } - - return true; -} - -///---- - -void -Lowerer::GenerateFastInlineStringSplitMatch(IR::Instr * instr) -{ - // a.split(b,c (optional) ) - // We want to emit the fast path when - // 1. c is not present, and - // 2. 'a' is a string and 'b' is a regex. - // - // a.match(b) - // We want to emit the fast path when 'a' is a string and 'b' is a regex. - - Assert(instr->m_opcode == Js::OpCode::CallDirect); - IR::Opnd * callDst = instr->GetDst(); - - //helperCallOpnd - IR::Opnd * src1 = instr->GetSrc1(); - - //ArgOut_A_InlineSpecialized - IR::Instr * tmpInstr = instr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef; - - IR::Opnd * argsOpnd[2]; - if(!instr->FetchOperands(argsOpnd, 2)) - { - return; - } - - if(!argsOpnd[0]->GetValueType().IsLikelyString() || argsOpnd[1]->IsTaggedInt()) - { - return; - } - - IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); - if(!argsOpnd[0]->GetValueType().IsString()) - { - argsOpnd[0] = GetRegOpnd(argsOpnd[0], instr, m_func, TyVar); - this->GenerateStringTest(argsOpnd[0]->AsRegOpnd(), instr, labelHelper); - } - - if(!argsOpnd[1]->IsNotTaggedValue()) - { - m_lowererMD.GenerateObjectTest(argsOpnd[1], instr, labelHelper); - } - - IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp); - - // cmp [regex], vtableAddress - // jne $labelHelper - argsOpnd[1] = GetRegOpnd(argsOpnd[1], instr, m_func, TyVar); - InsertCompareBranch( - IR::IndirOpnd::New(argsOpnd[1]->AsRegOpnd(), 0, TyMachPtr, instr->m_func), - vtableOpnd, - Js::OpCode::BrNeq_A, - labelHelper, - instr); - - IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); - if (callDst) - { - helperCallInstr->SetDst(callDst); - } - instr->InsertBefore(helperCallInstr); - if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind())) - { - helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr); - } - - // [stackAllocationPointer, ]scriptcontext, regexp, input[, limit] (to be pushed in reverse order) - - if(src1->AsHelperCallOpnd()->m_fnHelper == IR::JnHelperMethod::HelperString_Split) - { - //limit - //As we are optimizing only for two operands, make limit UINT_MAX - IR::Opnd* limit = IR::IntConstOpnd::New(UINT_MAX, TyUint32, instr->m_func); - this->m_lowererMD.LoadHelperArgument(helperCallInstr, limit); - } - - //input, regexp - this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[0]); - this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[1]); - - // script context - LoadScriptContext(helperCallInstr); - - IR::JnHelperMethod helperMethod = IR::JnHelperMethod::HelperInvalid; - IR::AutoReuseOpnd autoReuseStackAllocationOpnd; - if(callDst && instr->dstIsTempObject) - { - switch(src1->AsHelperCallOpnd()->m_fnHelper) - { - case IR::JnHelperMethod::HelperString_Split: - helperMethod = IR::JnHelperMethod::HelperRegExp_SplitResultUsedAndMayBeTemp; - break; - - case IR::JnHelperMethod::HelperString_Match: - helperMethod = IR::JnHelperMethod::HelperRegExp_MatchResultUsedAndMayBeTemp; - break; - - default: - Assert(false); - __assume(false); - } - - // Allocate some space on the stack for the result array - IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func); - autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func); - stackAllocationOpnd->SetValueType(callDst->GetValueType()); - GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr); - m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd); - } - else - { - switch(src1->AsHelperCallOpnd()->m_fnHelper) - { - case IR::JnHelperMethod::HelperString_Split: - helperMethod = - callDst - ? IR::JnHelperMethod::HelperRegExp_SplitResultUsed - : IR::JnHelperMethod::HelperRegExp_SplitResultNotUsed; - break; - - case IR::JnHelperMethod::HelperString_Match: - helperMethod = - callDst - ? IR::JnHelperMethod::HelperRegExp_MatchResultUsed - : IR::JnHelperMethod::HelperRegExp_MatchResultNotUsed; - break; - - default: - Assert(false); - __assume(false); - } - } - - m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod); - - IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); - instr->InsertAfter(doneLabel); - instr->InsertBefore(labelHelper); - InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper); - - RelocateCallDirectToHelperPath(tmpInstr, labelHelper); -} - -void -Lowerer::GenerateFastInlineRegExpExec(IR::Instr * instr) -{ - // a.exec(b) - // We want to emit the fast path when 'a' is a regex and 'b' is a string - - Assert(instr->m_opcode == Js::OpCode::CallDirect); - IR::Opnd * callDst = instr->GetDst(); - - //ArgOut_A_InlineSpecialized - IR::Instr * tmpInstr = instr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef; - - IR::Opnd * argsOpnd[2]; - if (!instr->FetchOperands(argsOpnd, 2)) - { - return; - } - - IR::Opnd *opndString = argsOpnd[1]; - if(!opndString->GetValueType().IsLikelyString() || argsOpnd[0]->IsTaggedInt()) - { - return; - } - - IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); - if(!opndString->GetValueType().IsString()) - { - opndString = GetRegOpnd(opndString, instr, m_func, TyVar); - this->GenerateStringTest(opndString->AsRegOpnd(), instr, labelHelper); - } - - IR::Opnd *opndRegex = argsOpnd[0]; - if(!opndRegex->IsNotTaggedValue()) - { - m_lowererMD.GenerateObjectTest(opndRegex, instr, labelHelper); - } - - IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp); - - // cmp [regex], vtableAddress - // jne $labelHelper - opndRegex = GetRegOpnd(opndRegex, instr, m_func, TyVar); - InsertCompareBranch( - IR::IndirOpnd::New(opndRegex->AsRegOpnd(), 0, TyMachPtr, instr->m_func), - vtableOpnd, - Js::OpCode::BrNeq_A, - labelHelper, - instr); - - IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func); - - if (!PHASE_OFF(Js::ExecBOIFastPathPhase, m_func)) - { - // Load pattern from regex operand - IR::RegOpnd *opndPattern = IR::RegOpnd::New(TyMachPtr, m_func); - Lowerer::InsertMove( - opndPattern, - IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfPattern(), TyMachPtr, m_func), - instr); - - // Load program from pattern - IR::RegOpnd *opndProgram = IR::RegOpnd::New(TyMachPtr, m_func); - Lowerer::InsertMove( - opndProgram, - IR::IndirOpnd::New(opndPattern, offsetof(UnifiedRegex::RegexPattern, rep) + offsetof(UnifiedRegex::RegexPattern::UnifiedRep, program), TyMachPtr, m_func), - instr); - - IR::LabelInstr *labelFastHelper = IR::LabelInstr::New(Js::OpCode::Label, m_func); - - // We want the program's tag to be BOILiteral2Tag - InsertCompareBranch( - IR::IndirOpnd::New(opndProgram, (int32)UnifiedRegex::Program::GetOffsetOfTag(), TyUint8, m_func), - IR::IntConstOpnd::New((IntConstType)UnifiedRegex::Program::GetBOILiteral2Tag(), TyUint8, m_func), - Js::OpCode::BrNeq_A, - labelFastHelper, - instr); - - // Test the program's flags for "global" - InsertTestBranch( - IR::IndirOpnd::New(opndProgram, offsetof(UnifiedRegex::Program, flags), TyUint8, m_func), - IR::IntConstOpnd::New(UnifiedRegex::GlobalRegexFlag, TyUint8, m_func), - Js::OpCode::BrNeq_A, - labelFastHelper, - instr); - - IR::LabelInstr *labelNoMatch = IR::LabelInstr::New(Js::OpCode::Label, m_func); - - // If string length < 2... - InsertCompareBranch( - IR::IndirOpnd::New(opndString->AsRegOpnd(), offsetof(Js::JavascriptString, m_charLength), TyUint32, m_func), - IR::IntConstOpnd::New(2, TyUint32, m_func), - Js::OpCode::BrLt_A, - labelNoMatch, - instr); - - // ...or the DWORD doesn't match the pattern... - IR::RegOpnd *opndBuffer = IR::RegOpnd::New(TyMachReg, m_func); - Lowerer::InsertMove( - opndBuffer, - IR::IndirOpnd::New(opndString->AsRegOpnd(), offsetof(Js::JavascriptString, m_pszValue), TyMachPtr, m_func), - instr); - - IR::LabelInstr *labelGotString = IR::LabelInstr::New(Js::OpCode::Label, m_func); - - InsertTestBranch(opndBuffer, opndBuffer, Js::OpCode::BrNeq_A, labelGotString, instr); - - m_lowererMD.LoadHelperArgument(instr, opndString); - IR::Instr *instrCall = IR::Instr::New(Js::OpCode::Call, opndBuffer, IR::HelperCallOpnd::New(IR::HelperString_GetSz, m_func), m_func); - instr->InsertBefore(instrCall); - m_lowererMD.LowerCall(instrCall, 0); - - instr->InsertBefore(labelGotString); - - IR::RegOpnd *opndBufferDWORD = IR::RegOpnd::New(TyUint32, m_func); - Lowerer::InsertMove( - opndBufferDWORD, - IR::IndirOpnd::New(opndBuffer, 0, TyUint32, m_func), - instr); + return false; + // // a.replace(b,c) + // // We want to emit the fast path if 'a' and 'c' are strings and 'b' is a regex + // // + // // strOpnd --> a + // // src1 --> b + // // src2 --> c - InsertCompareBranch( - IR::IndirOpnd::New(opndProgram, (int32)(UnifiedRegex::Program::GetOffsetOfRep() + UnifiedRegex::Program::GetOffsetOfBOILiteral2Literal()), TyUint32, m_func), - opndBufferDWORD, - Js::OpCode::BrEq_A, - labelFastHelper, - instr); + // IR::Opnd * callDst = callInstr->GetDst(); - // ...then set the last index to 0... - instr->InsertBefore(labelNoMatch); + // Assert(strOpnd->GetValueType().IsLikelyString() && src2->GetValueType().IsLikelyString()); - Lowerer::InsertMove( - IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfLastIndexVar(), TyVar, m_func), - IR::AddrOpnd::NewNull(m_func), - instr); + // if(!strOpnd->GetValueType().IsString()) + // { + // strOpnd = GetRegOpnd(strOpnd, insertInstr, m_func, TyVar); - Lowerer::InsertMove( - IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfLastIndexOrFlag(), TyUint32, m_func), - IR::IntConstOpnd::New(0, TyUint32, m_func), - instr); + // this->GenerateStringTest(strOpnd->AsRegOpnd(), insertInstr, labelHelper); + // } - // ...and set the dst to null... - if (callDst) - { - Lowerer::InsertMove( - callDst, - LoadLibraryValueOpnd(instr, LibraryValue::ValueNull), - instr); - } + // if(!src1->IsNotTaggedValue()) + // { + // m_lowererMD.GenerateObjectTest(src1, insertInstr, labelHelper); + // } - // ...and we're done. - this->InsertBranch(Js::OpCode::Br, doneLabel, instr); + // IR::Opnd * vtableOpnd = LoadVTableValueOpnd(insertInstr, VTableValue::VtableJavascriptRegExp); - instr->InsertBefore(labelFastHelper); - } + // // cmp [regex], vtableAddress + // // jne $labelHelper + // src1 = GetRegOpnd(src1, insertInstr, m_func, TyVar); + // InsertCompareBranch( + // IR::IndirOpnd::New(src1->AsRegOpnd(), 0, TyMachPtr, insertInstr->m_func), + // vtableOpnd, + // Js::OpCode::BrNeq_A, + // labelHelper, + // insertInstr); - IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); - if (callDst) - { - helperCallInstr->SetDst(callDst); - } - instr->InsertBefore(helperCallInstr); - if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind())) - { - helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr); - } - // [stackAllocationPointer, ]scriptcontext, regexp, string (to be pushed in reverse order) + // if(!src2->GetValueType().IsString()) + // { + // src2 = GetRegOpnd(src2, insertInstr, m_func, TyVar); + // this->GenerateStringTest(src2->AsRegOpnd(), insertInstr, labelHelper); + // } - //string, regexp - this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndString); - this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndRegex); + // IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, insertInstr->m_func); + // if (callDst) + // { + // helperCallInstr->SetDst(callDst); + // } + // insertInstr->InsertBefore(helperCallInstr); - // script context - LoadScriptContext(helperCallInstr); + // if (insertInstr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(insertInstr->GetBailOutKind())) + // { + // helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, insertInstr->GetBailOutInfo(), insertInstr->GetBailOutKind(), insertInstr); + // } - IR::JnHelperMethod helperMethod; - IR::AutoReuseOpnd autoReuseStackAllocationOpnd; - if (callDst) - { - if (instr->dstIsTempObject) - { - helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultUsedAndMayBeTemp; + // //scriptContext, pRegEx, pThis, pReplace (to be pushed in reverse order) - // Allocate some space on the stack for the result array - IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func); - autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func); - stackAllocationOpnd->SetValueType(callDst->GetValueType()); - GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr); - m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd); - } - else - { - helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultUsed; - } - } - else - { - helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultNotUsed; - } + // // pReplace, pThis, pRegEx + // this->m_lowererMD.LoadHelperArgument(helperCallInstr, src2); + // this->m_lowererMD.LoadHelperArgument(helperCallInstr, strOpnd); + // this->m_lowererMD.LoadHelperArgument(helperCallInstr, src1); - m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod); + // // script context + // LoadScriptContext(helperCallInstr); - instr->InsertAfter(doneLabel); - instr->InsertBefore(labelHelper); - InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper); + // if(callDst) + // { + // m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperRegExp_ReplaceStringResultUsed); + // } + // else + // { + // m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperRegExp_ReplaceStringResultNotUsed); + // } - RelocateCallDirectToHelperPath(tmpInstr, labelHelper); -} + // return true; +} + +// ///---- + +// void +// Lowerer::GenerateFastInlineStringSplitMatch(IR::Instr * instr) +// { +// // a.split(b,c (optional) ) +// // We want to emit the fast path when +// // 1. c is not present, and +// // 2. 'a' is a string and 'b' is a regex. +// // +// // a.match(b) +// // We want to emit the fast path when 'a' is a string and 'b' is a regex. + +// Assert(instr->m_opcode == Js::OpCode::CallDirect); +// IR::Opnd * callDst = instr->GetDst(); + +// //helperCallOpnd +// IR::Opnd * src1 = instr->GetSrc1(); + +// //ArgOut_A_InlineSpecialized +// IR::Instr * tmpInstr = instr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef; + +// IR::Opnd * argsOpnd[2]; +// if(!instr->FetchOperands(argsOpnd, 2)) +// { +// return; +// } + +// if(!argsOpnd[0]->GetValueType().IsLikelyString() || argsOpnd[1]->IsTaggedInt()) +// { +// return; +// } + +// IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); +// if(!argsOpnd[0]->GetValueType().IsString()) +// { +// argsOpnd[0] = GetRegOpnd(argsOpnd[0], instr, m_func, TyVar); +// this->GenerateStringTest(argsOpnd[0]->AsRegOpnd(), instr, labelHelper); +// } + +// if(!argsOpnd[1]->IsNotTaggedValue()) +// { +// m_lowererMD.GenerateObjectTest(argsOpnd[1], instr, labelHelper); +// } + +// IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp); + +// // cmp [regex], vtableAddress +// // jne $labelHelper +// argsOpnd[1] = GetRegOpnd(argsOpnd[1], instr, m_func, TyVar); +// InsertCompareBranch( +// IR::IndirOpnd::New(argsOpnd[1]->AsRegOpnd(), 0, TyMachPtr, instr->m_func), +// vtableOpnd, +// Js::OpCode::BrNeq_A, +// labelHelper, +// instr); + +// IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); +// if (callDst) +// { +// helperCallInstr->SetDst(callDst); +// } +// instr->InsertBefore(helperCallInstr); +// if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind())) +// { +// helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr); +// } + +// // [stackAllocationPointer, ]scriptcontext, regexp, input[, limit] (to be pushed in reverse order) + +// if(src1->AsHelperCallOpnd()->m_fnHelper == IR::JnHelperMethod::HelperString_Split) +// { +// //limit +// //As we are optimizing only for two operands, make limit UINT_MAX +// IR::Opnd* limit = IR::IntConstOpnd::New(UINT_MAX, TyUint32, instr->m_func); +// this->m_lowererMD.LoadHelperArgument(helperCallInstr, limit); +// } + +// //input, regexp +// this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[0]); +// this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[1]); + +// // script context +// LoadScriptContext(helperCallInstr); + +// IR::JnHelperMethod helperMethod = IR::JnHelperMethod::HelperInvalid; +// IR::AutoReuseOpnd autoReuseStackAllocationOpnd; +// if(callDst && instr->dstIsTempObject) +// { +// switch(src1->AsHelperCallOpnd()->m_fnHelper) +// { +// case IR::JnHelperMethod::HelperString_Split: +// helperMethod = IR::JnHelperMethod::HelperRegExp_SplitResultUsedAndMayBeTemp; +// break; + +// case IR::JnHelperMethod::HelperString_Match: +// helperMethod = IR::JnHelperMethod::HelperRegExp_MatchResultUsedAndMayBeTemp; +// break; + +// default: +// Assert(false); +// __assume(false); +// } + +// // Allocate some space on the stack for the result array +// IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func); +// autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func); +// stackAllocationOpnd->SetValueType(callDst->GetValueType()); +// GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr); +// m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd); +// } +// else +// { +// switch(src1->AsHelperCallOpnd()->m_fnHelper) +// { +// case IR::JnHelperMethod::HelperString_Split: +// helperMethod = +// callDst +// ? IR::JnHelperMethod::HelperRegExp_SplitResultUsed +// : IR::JnHelperMethod::HelperRegExp_SplitResultNotUsed; +// break; + +// case IR::JnHelperMethod::HelperString_Match: +// helperMethod = +// callDst +// ? IR::JnHelperMethod::HelperRegExp_MatchResultUsed +// : IR::JnHelperMethod::HelperRegExp_MatchResultNotUsed; +// break; + +// default: +// Assert(false); +// __assume(false); +// } +// } + +// m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod); + +// IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); +// instr->InsertAfter(doneLabel); +// instr->InsertBefore(labelHelper); +// InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper); + +// RelocateCallDirectToHelperPath(tmpInstr, labelHelper); +// } + +// void +// Lowerer::GenerateFastInlineRegExpExec(IR::Instr * instr) +// { +// // a.exec(b) +// // We want to emit the fast path when 'a' is a regex and 'b' is a string + +// Assert(instr->m_opcode == Js::OpCode::CallDirect); +// IR::Opnd * callDst = instr->GetDst(); + +// //ArgOut_A_InlineSpecialized +// IR::Instr * tmpInstr = instr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef; + +// IR::Opnd * argsOpnd[2]; +// if (!instr->FetchOperands(argsOpnd, 2)) +// { +// return; +// } + +// IR::Opnd *opndString = argsOpnd[1]; +// if(!opndString->GetValueType().IsLikelyString() || argsOpnd[0]->IsTaggedInt()) +// { +// return; +// } + +// IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); +// if(!opndString->GetValueType().IsString()) +// { +// opndString = GetRegOpnd(opndString, instr, m_func, TyVar); +// this->GenerateStringTest(opndString->AsRegOpnd(), instr, labelHelper); +// } + +// IR::Opnd *opndRegex = argsOpnd[0]; +// if(!opndRegex->IsNotTaggedValue()) +// { +// m_lowererMD.GenerateObjectTest(opndRegex, instr, labelHelper); +// } + +// IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp); + +// // cmp [regex], vtableAddress +// // jne $labelHelper +// opndRegex = GetRegOpnd(opndRegex, instr, m_func, TyVar); +// InsertCompareBranch( +// IR::IndirOpnd::New(opndRegex->AsRegOpnd(), 0, TyMachPtr, instr->m_func), +// vtableOpnd, +// Js::OpCode::BrNeq_A, +// labelHelper, +// instr); + +// IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func); + +// if (!PHASE_OFF(Js::ExecBOIFastPathPhase, m_func)) +// { +// // Load pattern from regex operand +// IR::RegOpnd *opndPattern = IR::RegOpnd::New(TyMachPtr, m_func); +// Lowerer::InsertMove( +// opndPattern, +// IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfPattern(), TyMachPtr, m_func), +// instr); + +// // Load program from pattern +// IR::RegOpnd *opndProgram = IR::RegOpnd::New(TyMachPtr, m_func); +// Lowerer::InsertMove( +// opndProgram, +// IR::IndirOpnd::New(opndPattern, offsetof(UnifiedRegex::RegexPattern, rep) + offsetof(UnifiedRegex::RegexPattern::UnifiedRep, program), TyMachPtr, m_func), +// instr); + +// IR::LabelInstr *labelFastHelper = IR::LabelInstr::New(Js::OpCode::Label, m_func); + +// // We want the program's tag to be BOILiteral2Tag +// InsertCompareBranch( +// IR::IndirOpnd::New(opndProgram, (int32)UnifiedRegex::Program::GetOffsetOfTag(), TyUint8, m_func), +// IR::IntConstOpnd::New((IntConstType)UnifiedRegex::Program::GetBOILiteral2Tag(), TyUint8, m_func), +// Js::OpCode::BrNeq_A, +// labelFastHelper, +// instr); + +// // Test the program's flags for "global" +// InsertTestBranch( +// IR::IndirOpnd::New(opndProgram, offsetof(UnifiedRegex::Program, flags), TyUint8, m_func), +// IR::IntConstOpnd::New(UnifiedRegex::GlobalRegexFlag, TyUint8, m_func), +// Js::OpCode::BrNeq_A, +// labelFastHelper, +// instr); + +// IR::LabelInstr *labelNoMatch = IR::LabelInstr::New(Js::OpCode::Label, m_func); + +// // If string length < 2... +// InsertCompareBranch( +// IR::IndirOpnd::New(opndString->AsRegOpnd(), offsetof(Js::JavascriptString, m_charLength), TyUint32, m_func), +// IR::IntConstOpnd::New(2, TyUint32, m_func), +// Js::OpCode::BrLt_A, +// labelNoMatch, +// instr); + +// // ...or the DWORD doesn't match the pattern... +// IR::RegOpnd *opndBuffer = IR::RegOpnd::New(TyMachReg, m_func); +// Lowerer::InsertMove( +// opndBuffer, +// IR::IndirOpnd::New(opndString->AsRegOpnd(), offsetof(Js::JavascriptString, m_pszValue), TyMachPtr, m_func), +// instr); + +// IR::LabelInstr *labelGotString = IR::LabelInstr::New(Js::OpCode::Label, m_func); + +// InsertTestBranch(opndBuffer, opndBuffer, Js::OpCode::BrNeq_A, labelGotString, instr); + +// m_lowererMD.LoadHelperArgument(instr, opndString); +// IR::Instr *instrCall = IR::Instr::New(Js::OpCode::Call, opndBuffer, IR::HelperCallOpnd::New(IR::HelperString_GetSz, m_func), m_func); +// instr->InsertBefore(instrCall); +// m_lowererMD.LowerCall(instrCall, 0); + +// instr->InsertBefore(labelGotString); + +// IR::RegOpnd *opndBufferDWORD = IR::RegOpnd::New(TyUint32, m_func); +// Lowerer::InsertMove( +// opndBufferDWORD, +// IR::IndirOpnd::New(opndBuffer, 0, TyUint32, m_func), +// instr); + +// InsertCompareBranch( +// IR::IndirOpnd::New(opndProgram, (int32)(UnifiedRegex::Program::GetOffsetOfRep() + UnifiedRegex::Program::GetOffsetOfBOILiteral2Literal()), TyUint32, m_func), +// opndBufferDWORD, +// Js::OpCode::BrEq_A, +// labelFastHelper, +// instr); + +// // ...then set the last index to 0... +// instr->InsertBefore(labelNoMatch); + +// Lowerer::InsertMove( +// IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfLastIndexVar(), TyVar, m_func), +// IR::AddrOpnd::NewNull(m_func), +// instr); + +// Lowerer::InsertMove( +// IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfLastIndexOrFlag(), TyUint32, m_func), +// IR::IntConstOpnd::New(0, TyUint32, m_func), +// instr); + +// // ...and set the dst to null... +// if (callDst) +// { +// Lowerer::InsertMove( +// callDst, +// LoadLibraryValueOpnd(instr, LibraryValue::ValueNull), +// instr); +// } + +// // ...and we're done. +// this->InsertBranch(Js::OpCode::Br, doneLabel, instr); + +// instr->InsertBefore(labelFastHelper); +// } + +// IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func); +// if (callDst) +// { +// helperCallInstr->SetDst(callDst); +// } +// instr->InsertBefore(helperCallInstr); +// if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind())) +// { +// helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr); +// } +// // [stackAllocationPointer, ]scriptcontext, regexp, string (to be pushed in reverse order) + +// //string, regexp +// this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndString); +// this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndRegex); + +// // script context +// LoadScriptContext(helperCallInstr); + +// IR::JnHelperMethod helperMethod; +// IR::AutoReuseOpnd autoReuseStackAllocationOpnd; +// if (callDst) +// { +// if (instr->dstIsTempObject) +// { +// helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultUsedAndMayBeTemp; + +// // Allocate some space on the stack for the result array +// IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func); +// autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func); +// stackAllocationOpnd->SetValueType(callDst->GetValueType()); +// GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr); +// m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd); +// } +// else +// { +// helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultUsed; +// } +// } +// else +// { +// helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultNotUsed; +// } + +// m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod); + +// instr->InsertAfter(doneLabel); +// instr->InsertBefore(labelHelper); +// InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper); + +// RelocateCallDirectToHelperPath(tmpInstr, labelHelper); +// } // Generate a fast path for the "in" operator that check quickly if we have an array or not and if the index of the data is contained in the array's length. void Lowerer::GenerateFastArrayIsIn(IR::Instr * instr) diff --git a/lib/Backend/Lower.h b/lib/Backend/Lower.h index ef670d66338..07ed08c6020 100644 --- a/lib/Backend/Lower.h +++ b/lib/Backend/Lower.h @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #pragma once @@ -539,14 +540,15 @@ class Lowerer void GenerateFastInlineHasOwnProperty(IR::Instr * instr); void GenerateFastInlineArrayPush(IR::Instr * instr); void GenerateFastInlineArrayPop(IR::Instr * instr); - void GenerateFastInlineStringSplitMatch(IR::Instr * instr); + // TODO: Cleanup + // void GenerateFastInlineStringSplitMatch(IR::Instr * instr); void GenerateFastInlineMathImul(IR::Instr* instr); void GenerateFastInlineMathClz(IR::Instr* instr); void GenerateCtz(IR::Instr* instr); void GeneratePopCnt(IR::Instr* instr); template void GenerateTruncWithCheck(_In_ IR::Instr* instr); void GenerateFastInlineMathFround(IR::Instr* instr); - void GenerateFastInlineRegExpExec(IR::Instr * instr); + // void GenerateFastInlineRegExpExec(IR::Instr * instr); bool GenerateFastPush(IR::Opnd *baseOpndParam, IR::Opnd *src, IR::Instr *callInstr, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel, IR::LabelInstr * bailOutLabelHelper, bool returnLength = false); bool GenerateFastReplace(IR::Opnd* strOpnd, IR::Opnd* src1, IR::Opnd* src2, IR::Instr *callInstr, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel); bool ShouldGenerateStringReplaceFastPath(IR::Instr * instr, IntConstType argCount); diff --git a/lib/Backend/TempTracker.cpp b/lib/Backend/TempTracker.cpp index 156dc48b07a..9b616d8cb5d 100644 --- a/lib/Backend/TempTracker.cpp +++ b/lib/Backend/TempTracker.cpp @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "Backend.h" @@ -1165,33 +1166,33 @@ ObjectTemp::ProcessBailOnNoProfile(IR::Instr * instr) void ObjectTemp::ProcessInstr(IR::Instr * instr) { - if (instr->m_opcode != Js::OpCode::CallDirect) - { - return; - } - - IR::HelperCallOpnd * helper = instr->GetSrc1()->AsHelperCallOpnd(); - switch (helper->m_fnHelper) - { - case IR::JnHelperMethod::HelperString_Match: - case IR::JnHelperMethod::HelperString_Replace: - { - // First (non-this) parameter is either a regexp or search string. - // It doesn't escape. - IR::Instr * instrArgDef = nullptr; - instr->FindCallArgumentOpnd(2, &instrArgDef); - instrArgDef->dstIsTempObject = true; - break; - } - - case IR::JnHelperMethod::HelperRegExp_Exec: - { - IR::Instr * instrArgDef = nullptr; - instr->FindCallArgumentOpnd(1, &instrArgDef); - instrArgDef->dstIsTempObject = true; - break; - } - }; + // if (instr->m_opcode != Js::OpCode::CallDirect) + // { + // return; + // } + + // IR::HelperCallOpnd * helper = instr->GetSrc1()->AsHelperCallOpnd(); + // switch (helper->m_fnHelper) + // { + // case IR::JnHelperMethod::HelperString_Match: + // case IR::JnHelperMethod::HelperString_Replace: + // { + // // First (non-this) parameter is either a regexp or search string. + // // It doesn't escape. + // IR::Instr * instrArgDef = nullptr; + // instr->FindCallArgumentOpnd(2, &instrArgDef); + // instrArgDef->dstIsTempObject = true; + // break; + // } + + // case IR::JnHelperMethod::HelperRegExp_Exec: + // { + // IR::Instr * instrArgDef = nullptr; + // instr->FindCallArgumentOpnd(1, &instrArgDef); + // instrArgDef->dstIsTempObject = true; + // break; + // } + // }; } @@ -1354,33 +1355,33 @@ ObjectTempVerify::ProcessInstr(IR::Instr * instr, BackwardPass * backwardPass) return; } - if (instr->m_opcode != Js::OpCode::CallDirect) - { - return; - } - - IR::HelperCallOpnd * helper = instr->GetSrc1()->AsHelperCallOpnd(); - switch (helper->m_fnHelper) - { - case IR::JnHelperMethod::HelperString_Match: - case IR::JnHelperMethod::HelperString_Replace: - { - // First (non-this) parameter is either a regexp or search string - // It doesn't escape - IR::Instr * instrArgDef; - instr->FindCallArgumentOpnd(2, &instrArgDef); - Assert(instrArgDef->dstIsTempObject); - break; - } - - case IR::JnHelperMethod::HelperRegExp_Exec: - { - IR::Instr * instrArgDef; - instr->FindCallArgumentOpnd(1, &instrArgDef); - Assert(instrArgDef->dstIsTempObject); - break; - } - }; + // if (instr->m_opcode != Js::OpCode::CallDirect) + // { + // return; + // } + + // IR::HelperCallOpnd * helper = instr->GetSrc1()->AsHelperCallOpnd(); + // switch (helper->m_fnHelper) + // { + // case IR::JnHelperMethod::HelperString_Match: + // case IR::JnHelperMethod::HelperString_Replace: + // { + // // First (non-this) parameter is either a regexp or search string + // // It doesn't escape + // IR::Instr * instrArgDef; + // instr->FindCallArgumentOpnd(2, &instrArgDef); + // Assert(instrArgDef->dstIsTempObject); + // break; + // } + + // case IR::JnHelperMethod::HelperRegExp_Exec: + // { + // IR::Instr * instrArgDef; + // instr->FindCallArgumentOpnd(1, &instrArgDef); + // Assert(instrArgDef->dstIsTempObject); + // break; + // } + // }; } diff --git a/lib/Runtime/Library/JavascriptRegularExpression.cpp b/lib/Runtime/Library/JavascriptRegularExpression.cpp index 62804ba4b80..087056f70f6 100644 --- a/lib/Runtime/Library/JavascriptRegularExpression.cpp +++ b/lib/Runtime/Library/JavascriptRegularExpression.cpp @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "RuntimeLibraryPch.h" @@ -811,12 +812,25 @@ using namespace Js; JavascriptString* string = GetFirstStringArg(args, scriptContext); + // 4. Let previousLastIndex be ? Get(rx, "lastIndex"). Var previousLastIndex = JavascriptOperators::GetProperty(thisObj, PropertyIds::lastIndex, scriptContext); - SetLastIndexProperty(regEx, TaggedInt::ToVarUnchecked(0), scriptContext); + // 5. If SameValue(previousLastIndex, +0F) is false, then + // a. Perform ? Set(rx, "lastIndex", +0F, true). + if (!JavascriptConversion::SameValue(previousLastIndex, TaggedInt::ToVarUnchecked(0))) + { + SetLastIndexProperty(regEx, TaggedInt::ToVarUnchecked(0), scriptContext); + } Var result = CallExec(thisObj, string, varName, scriptContext); - - SetLastIndexProperty(regEx, previousLastIndex, scriptContext); + + // 7. Let currentLastIndex be ? Get(rx, "lastIndex"). + Var currentLastIndex = JavascriptOperators::GetProperty(thisObj, PropertyIds::lastIndex, scriptContext); + // 8. If SameValue(currentLastIndex, previousLastIndex) is false, then + // a. Perform ? Set(rx, "lastIndex", previousLastIndex, true). + if (!JavascriptConversion::SameValue(currentLastIndex, previousLastIndex)) + { + SetLastIndexProperty(regEx, previousLastIndex, scriptContext); + } return JavascriptOperators::IsNull(result) ? TaggedInt::ToVarUnchecked(-1) @@ -833,28 +847,15 @@ using namespace Js; CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, RegexSymbolSplit, scriptContext); - RecyclableObject *thisObj = GetThisObject(args, _u("RegExp.prototype[Symbol.match]"), scriptContext); + // ! Fun Note: In some time there was RegExp.prototype[Symbol.match] Lol + RecyclableObject *thisObj = GetThisObject(args, _u("RegExp.prototype[Symbol.split]"), scriptContext); JavascriptString* string = GetFirstStringArg(args, scriptContext); - // TODO: SPEC DEVIATION - // - // In RegexHelper::RegexSplit, we check if RegExp properties are overridden in order to determine - // if the algorithm is observable. If it is, we go through the new ES6 algorithm, but otherwise, we - // run the faster ES5 version. - // - // According to the spec, we're supposed to process "limit" after we use some of the RegExp properties. - // However, there doesn't seem to be any reason why "limit" processing can't be pulled above the rest - // in the spec. Therefore, we should see if such a spec update is OK. If not, this would have to be - // moved to its correct place in the code. - uint32 limit = (args.Info.Count < 3 || JavascriptOperators::IsUndefinedObject(args[2])) - ? UINT_MAX - : JavascriptConversion::ToUInt32(args[2], scriptContext); - return RegexHelper::RegexSplit( scriptContext, thisObj, string, - limit, + args, RegexHelper::IsResultNotUsed(callInfo.Flags)); } diff --git a/lib/Runtime/Library/JavascriptString.cpp b/lib/Runtime/Library/JavascriptString.cpp index bcd6b45bb6d..c906a20b87e 100644 --- a/lib/Runtime/Library/JavascriptString.cpp +++ b/lib/Runtime/Library/JavascriptString.cpp @@ -1811,10 +1811,11 @@ namespace Js Var JavascriptString::GetRegExSymbolFunction(Var regExp, PropertyId propertyId, ScriptContext* scriptContext) { - return JavascriptOperators::GetPropertyNoCache( + Var func = JavascriptOperators::GetPropertyNoCache( JavascriptOperators::ToObject(regExp, scriptContext), propertyId, scriptContext); + return JavascriptOperators::IsUndefinedOrNull(func) ? scriptContext->GetLibrary()->GetUndefined() : func; } template @@ -1915,6 +1916,7 @@ namespace Js return SubstringCore(pThis, idxStart, idxEnd - idxStart, scriptContext); } + // ES6: 22.1.3.21 String.prototype.split ( separator, limit ) Var JavascriptString::EntrySplit(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); @@ -1928,65 +1930,141 @@ namespace Js AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, varName); - auto fallback = [&](JavascriptString* stringObj) + if (args.Info.Count < 1 || JavascriptOperators::IsUndefinedOrNull(args[0])) { - return DoStringSplit(args, callInfo, stringObj, scriptContext); - }; - return DelegateToRegExSymbolFunction<2>(args, PropertyIds::_symbolSplit, fallback, varName, scriptContext); + JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NullOrUndefined, varName); + } + + if (args.Info.Count > 1 && !JavascriptOperators::IsUndefinedOrNull(args[1])) + { + Var splitter = GetRegExSymbolFunction(args[1], PropertyIds::_symbolSplit, scriptContext); + if (!JavascriptOperators::IsUndefined(splitter)) + { + Var limit = args.Info.Count > 2 ? args[2] : scriptContext->GetLibrary()->GetUndefined(); + + if (!JavascriptConversion::IsCallable(splitter)) + { + JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_Invalid, varName); + } + + // ! NOTE: Execute with exactly 2 arguments + ThreadContext* threadContext = scriptContext->GetThreadContext(); + return threadContext->ExecuteImplicitCall(UnsafeVarTo(splitter), ImplicitCall_Accessor, [=]()->Js::Var + { + Var result = nullptr; + result = CALL_FUNCTION(threadContext, UnsafeVarTo(splitter), CallInfo(CallFlags_Value, 3), args[1], args[0], limit); + return result; + }); + } + } + + + return DoStringSplit(args, scriptContext); } - Var JavascriptString::DoStringSplit(Arguments& args, CallInfo& callInfo, JavascriptString* input, ScriptContext* scriptContext) + Var JavascriptString::DoStringSplit(Arguments& args, ScriptContext* scriptContext) { - if (args.Info.Count == 1) + JavascriptString* S = JavascriptConversion::ToString(args[0], scriptContext); + JavascriptArray* A = scriptContext->GetLibrary()->CreateArray((uint32_t)0); + uint32_t lengthA = 0; + uint32_t limit = args.Info.Count > 2 && !JavascriptOperators::IsUndefined(args[2]) ? + JavascriptConversion::ToUInt32(args[2], scriptContext) : UINT32_MAX; + + Var separator = args.Info.Count > 1 ? args[1] : scriptContext->GetLibrary()->GetUndefined(); + JavascriptString* R = JavascriptConversion::ToString(separator, scriptContext); + + if (limit == 0) { - JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(1); - ary->DirectSetItemAt(0, input); - return ary; + return A; } - else + + if (JavascriptOperators::IsUndefined(separator)) { - uint32 limit; - if (args.Info.Count < 3 || JavascriptOperators::IsUndefinedObject(args[2])) - { - limit = UINT_MAX; - } - else + A->DirectAppendItem(S); + return A; + } + + charcount_t s = S->GetLength(); + + if (s == 0) + { + if (R->GetLength() != 0) { - limit = JavascriptConversion::ToUInt32(args[2], scriptContext); + A->DirectAppendItem(S); } + return A; + } + + charcount_t p = 0; + charcount_t q = p; + + do + { + charcount_t e; - // When the config is enabled, the operation is handled by RegExp.prototype[@@split]. - if (!scriptContext->GetConfig()->IsES6RegExSymbolsEnabled() - && VarIs(args[1])) + if (!SplitMatch(S, q, R, &e)) { - return RegexHelper::RegexSplit(scriptContext, UnsafeVarTo(args[1]), input, limit, - RegexHelper::IsResultNotUsed(callInfo.Flags)); + q++; } else { - JavascriptString* separator = JavascriptConversion::ToString(args[1], scriptContext); - - if (callInfo.Flags & CallFlags_NotUsed) + Assert(e <= s); + if (e == p) { - return scriptContext->GetLibrary()->GetNull(); + q++; } - - if (!limit) + else { - JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(0); - return ary; - } +#ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS + S = (JavascriptString*)BreakSpeculation(S); +#endif - if (JavascriptOperators::GetTypeId(args[1]) == TypeIds_Undefined) - { - JavascriptArray* ary = scriptContext->GetLibrary()->CreateArray(1); - ary->DirectSetItemAt(0, input); - return ary; - } + Var T = SubstringCore(S, p, q - p, scriptContext); + + A->DirectAppendItem(T); - return RegexHelper::StringSplit(separator, input, limit); + lengthA++; + + if (lengthA == limit) + { + return A; + } + + p = e; + q = p; + } + } + } while (q != s); + +#ifdef ENABLE_SPECTRE_RUNTIME_MITIGATIONS + S = (JavascriptString*)BreakSpeculation(S); +#endif + + Var T = SubstringCore(S, p, s - p, scriptContext); + + A->DirectAppendItem(T); + + return A; + } + + // ES6: 22.1.3.21.1 SplitMatch ( S, q, R ) + bool JavascriptString::SplitMatch(JavascriptString* S, charcount_t q, JavascriptString* R, charcount_t* e) { + charcount_t s = S->GetLength(); + charcount_t r = R->GetLength(); + + if (q + r > s) + { + return false; + } + + if (wmemcmp(S->GetString() + q, R->GetString(), r) != 0) + { + return false; } + + *e = q + r; + return true; } Var JavascriptString::EntrySubstring(RecyclableObject* function, CallInfo callInfo, ...) @@ -2837,6 +2915,60 @@ namespace Js return m_pszValue; } + codepoint_t* JavascriptString::GetCodePoints(ScriptContext* scriptContext) + { + if (m_codePointString != nullptr) + { + return m_codePointString; + } + + const char16* initialString = GetString(); + charcount_t codePointLength = 0; + charcount_t stringLength = GetLength(), i = 0; + while (i < stringLength) + { + if (i + 1 < stringLength && + NumberUtilities::IsSurrogateLowerPart((codepoint_t)initialString[i]) && + NumberUtilities::IsSurrogateUpperPart((codepoint_t)initialString[i + 1])) + { + codePointLength++; + i++; + } + + i++; + } + codepoint_t* codePoints; + charcount_t j = 0; + i = 0; + codePoints = AllocatorNewArrayLeafZ(Recycler, scriptContext->GetRecycler(), codepoint_t, codePointLength); + while (i < stringLength) + { + if (i + 1 < stringLength && + NumberUtilities::IsSurrogateLowerPart((codepoint_t)initialString[i]) && + NumberUtilities::IsSurrogateUpperPart((codepoint_t)initialString[i + 1])) + { + *(codePoints + j++) = NumberUtilities::SurrogatePairAsCodePoint((codepoint_t)initialString[i], (codepoint_t)initialString[i + 1]); + i += 2ui32; + continue; + } + *(codePoints + j++) = (codepoint_t)initialString[i]; + i++; + } + m_codePointString = codePoints; + m_codePointsLength = codePointLength; + return codePoints; + } + + charcount_t JavascriptString::GetCodePointsLength() + { + if (m_codePointsLength == k_InvalidCharCount) + { + GetCodePoints(); // Init the m_codePointsLength and m_codePoints properties + } + + return m_codePointsLength; + } + void const * JavascriptString::GetOriginalStringReference() { // Just return the string buffer diff --git a/lib/Runtime/Library/JavascriptString.h b/lib/Runtime/Library/JavascriptString.h index 30cc4b60865..3f69f21caf4 100644 --- a/lib/Runtime/Library/JavascriptString.h +++ b/lib/Runtime/Library/JavascriptString.h @@ -40,8 +40,10 @@ namespace Js JavascriptString(JavascriptString&) = delete; private: - Field(const char16*) m_pszValue; // Flattened, '\0' terminated contents + Field(const char16*) m_pszValue; // Flattened, '\0' terminated contents Field(charcount_t) m_charLength; // Length in characters, not including '\0'. + Field(codepoint_t*) m_codePointString; // Code points of the string, may be nullptr if not initialized yet by GetCodePoints call + Field(charcount_t) m_codePointsLength; // Length of the codepoints, may be k_InvalidCharCount if not initialized yet by GetCodePoints call static const charcount_t MaxCharLength = INT_MAX - 1; // Max number of chars not including '\0'. @@ -69,6 +71,8 @@ namespace Js const char16* UnsafeGetBuffer() const; LPCWSTR GetSzCopy(ArenaAllocator* alloc); // Copy to an Arena const char16* GetString(); // Get string, may not be NULL terminated + codepoint_t* GetCodePoints(); + charcount_t GetCodePointsLength(); // NumberUtil::FIntRadStrToDbl and parts of GlobalObject::EntryParseInt were refactored into ToInteger Var ToInteger(int radix = 0); @@ -348,7 +352,8 @@ namespace Js static Var TrimLeftRightHelper(JavascriptString* arg, ScriptContext* scriptContext); static Var DoStringReplace(Arguments& args, CallInfo& callInfo, JavascriptString* input, ScriptContext* scriptContext); - static Var DoStringSplit(Arguments& args, CallInfo& callInfo, JavascriptString* input, ScriptContext* scriptContext); + static Var DoStringSplit(Arguments& args, ScriptContext* scriptContext); + static bool SplitMatch(JavascriptString* S, charcount_t q, JavascriptString* R, charcount_t* e); template static Var DelegateToRegExSymbolFunction(ArgumentReader &args, PropertyId symbolPropertyId, FallbackFn fallback, PCWSTR varName, ScriptContext* scriptContext); static Var GetRegExSymbolFunction(Var regExp, PropertyId propertyId, ScriptContext* scriptContext); diff --git a/lib/Runtime/Library/RegexHelper.cpp b/lib/Runtime/Library/RegexHelper.cpp index e65eb1bad9d..930d45f76a0 100644 --- a/lib/Runtime/Library/RegexHelper.cpp +++ b/lib/Runtime/Library/RegexHelper.cpp @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "RuntimeLibraryPch.h" @@ -364,25 +365,7 @@ namespace Js template Var RegexHelper::RegexMatchImpl(ScriptContext* scriptContext, RecyclableObject *thisObj, JavascriptString *input, bool noResult, void *const stackAllocationPointer) { - ScriptConfiguration const * scriptConfig = scriptContext->GetConfig(); - - // Normally, this check would be done in JavascriptRegExp::EntrySymbolMatch. However, - // since the lowerer inlines String.prototype.match and directly calls the helper, - // the check then would be bypassed. That's the reason we do the check here. - if (scriptConfig->IsES6RegExSymbolsEnabled() - && IsRegexSymbolMatchObservable(thisObj, scriptContext)) - { - // We don't need to pass "updateHistory" here since the call to "exec" will handle it. - return RegexEs6MatchImpl(scriptContext, thisObj, input, noResult, stackAllocationPointer); - } - else - { - PCWSTR varName = scriptConfig->IsES6RegExSymbolsEnabled() - ? _u("RegExp.prototype[Symbol.match]") - : _u("String.prototype.match"); - JavascriptRegExp* regularExpression = JavascriptRegExp::ToRegExp(thisObj, varName, scriptContext); - return RegexEs5MatchImpl(scriptContext, regularExpression, input, noResult, stackAllocationPointer); - } + return RegexEs6MatchImpl(scriptContext, thisObj, input, noResult, stackAllocationPointer); } bool RegexHelper::IsRegexSymbolMatchObservable(RecyclableObject* instance, ScriptContext* scriptContext) @@ -598,6 +581,50 @@ namespace Js return arrayResult; } + // // RegExpBuiltinExec (ES6 22.2.5.2.2) + // Var RegexHelper::RegexExecImpl(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, bool noResult, void *const stackAllocationPointer) + // { + // UnifiedRegex::RegexPattern* pattern = regularExpression->GetPattern(); + + // CharCount inputLength = input->GetLength(); + // /*Var lastIndex = */JavascriptOperators::GetProperty(regularExpression, PropertyIds::lastIndex, scriptContext); + // const bool isGlobal = pattern->IsGlobal(); + // const bool isSticky = pattern->IsSticky(); + // CharCount offset; + // if (!GetInitialOffset(isGlobal, isSticky, regularExpression, inputLength, offset)) + // { + // return scriptContext->GetLibrary()->GetNull(); + // } + + // UnifiedRegex::GroupInfo match; // initially undefined + // const char16* inputStr = input->GetString(); + // if (offset <= inputLength) + // { + // // TODO: Understand what SimpleMatch does + // match = SimpleMatch(scriptContext, pattern, inputStr, inputLength, offset); + // } + + // // else: match remains undefined + // PropagateLastMatch(scriptContext, isGlobal, isSticky, regularExpression, input, match, match, true, true); + + // if (noResult || match.IsUndefined()) + // { + // return scriptContext->GetLibrary()->GetNull(); + // } + + // const int numGroups = pattern->NumGroups(); + // Assert(numGroups >= 0); + // JavascriptArray* result = CreateExecResult(stackAllocationPointer, scriptContext, numGroups, input, match); + // Var nonMatchValue = NonMatchValue(scriptContext, false); + // Field(Var) *elements = ((SparseArraySegment*)result->GetHead())->elements; + // for (uint groupId = 0; groupId < (uint)numGroups; groupId++) + // { + // Assert(groupId < result->GetHead()->left + result->GetHead()->length); + // elements[groupId] = GetGroup(scriptContext, pattern, input, nonMatchValue, groupId); + // } + // return result; + // } + // RegExp.prototype.exec (ES5 15.10.6.2) Var RegexHelper::RegexExecImpl(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, bool noResult, void *const stackAllocationPointer) { @@ -893,20 +920,7 @@ namespace Js Var RegexHelper::RegexReplaceImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, JavascriptString* replace, bool noResult) { - ScriptConfiguration const * scriptConfig = scriptContext->GetConfig(); - - if (scriptConfig->IsES6RegExSymbolsEnabled() && IsRegexSymbolReplaceObservable(thisObj, scriptContext)) - { - return RegexEs6ReplaceImpl(scriptContext, thisObj, input, replace, noResult); - } - else - { - PCWSTR varName = scriptConfig->IsES6RegExSymbolsEnabled() - ? _u("RegExp.prototype[Symbol.replace]") - : _u("String.prototype.replace"); - JavascriptRegExp* regularExpression = JavascriptRegExp::ToRegExp(thisObj, varName, scriptContext); - return RegexEs5ReplaceImpl(scriptContext, regularExpression, input, replace, noResult); - } + return RegexEs6ReplaceImpl(scriptContext, thisObj, input, replace, noResult); } bool RegexHelper::IsRegexSymbolReplaceObservable(RecyclableObject* instance, ScriptContext* scriptContext) @@ -1576,23 +1590,9 @@ namespace Js return splitPattern; } - Var RegexHelper::RegexSplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer) + Var RegexHelper::RegexSplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, Arguments& args, bool noResult, void *const stackAllocationPointer) { - ScriptConfiguration const * scriptConfig = scriptContext->GetConfig(); - - if (scriptConfig->IsES6RegExSymbolsEnabled() - && IsRegexSymbolSplitObservable(thisObj, scriptContext)) - { - return RegexEs6SplitImpl(scriptContext, thisObj, input, limit, noResult, stackAllocationPointer); - } - else - { - PCWSTR varName = scriptContext->GetConfig()->IsES6RegExSymbolsEnabled() - ? _u("RegExp.prototype[Symbol.split]") - : _u("String.prototype.split"); - JavascriptRegExp* regularExpression = JavascriptRegExp::ToRegExp(thisObj, varName, scriptContext); - return RegexEs5SplitImpl(scriptContext, regularExpression, input, limit, noResult, stackAllocationPointer); - } + return RegexEs6SplitImpl(scriptContext, thisObj, input, args, noResult, stackAllocationPointer); } bool RegexHelper::IsRegexSymbolSplitObservable(RecyclableObject* instance, ScriptContext* scriptContext) @@ -1604,7 +1604,7 @@ namespace Js || JavascriptRegExp::HasObservableExec(regexPrototype); } - Var RegexHelper::RegexEs6SplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer) + Var RegexHelper::RegexEs6SplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, Arguments& args, bool noResult, void *const stackAllocationPointer) { PCWSTR const varName = _u("RegExp.prototype[Symbol.split]"); @@ -1635,12 +1635,17 @@ namespace Js JavascriptArray* arrayResult = scriptContext->GetLibrary()->CreateArray(); + uint32 limit = (args.Info.Count < 3 || JavascriptOperators::IsUndefinedObject(args[2])) + ? UINT_MAX + : JavascriptConversion::ToUInt32(args[2], scriptContext); + + CharCount inputLength = input->GetLength(); // 'size' in spec + if (limit == 0) { return arrayResult; } - CharCount inputLength = input->GetLength(); if (inputLength == 0) { Var result = JavascriptRegExp::CallExec(splitter, input, varName, scriptContext); @@ -1727,6 +1732,7 @@ namespace Js return flags; } + // TODO: Cleanup // String.prototype.split (ES5 15.5.4.14) Var RegexHelper::RegexEs5SplitImpl(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer) { @@ -1946,6 +1952,8 @@ namespace Js #endif ); + + #if ENABLE_REGEX_CONFIG_OPTIONS if (REGEX_CONFIG_FLAG(RegexProfile)) scriptContext->GetRegexStatsDatabase()->EndProfile(stats, UnifiedRegex::RegexStats::Execute); @@ -2215,27 +2223,28 @@ namespace Js return value; } - Var RegexHelper::RegexMatchResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) - { - return RegexHelper::RegexMatch(scriptContext, regularExpression, input, false); - } - - Var RegexHelper::RegexMatchResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) - { - return RegexHelper::RegexMatch(scriptContext, regularExpression, input, false, stackAllocationPointer); - } - - Var RegexHelper::RegexMatchResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) - { - if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) - { - return RegexHelper::RegexMatch(scriptContext, regularExpression, input, true); - } - else - { - return RegexHelper::RegexMatch(scriptContext, regularExpression, input, false); - } - } + // TODO: Cleanup + // Var RegexHelper::RegexMatchResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) + // { + // return RegexHelper::RegexMatch(scriptContext, regularExpression, input, false); + // } + + // Var RegexHelper::RegexMatchResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) + // { + // return RegexHelper::RegexMatch(scriptContext, regularExpression, input, false, stackAllocationPointer); + // } + + // Var RegexHelper::RegexMatchResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) + // { + // if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) + // { + // return RegexHelper::RegexMatch(scriptContext, regularExpression, input, true); + // } + // else + // { + // return RegexHelper::RegexMatch(scriptContext, regularExpression, input, false); + // } + // } Var RegexHelper::RegexMatch(ScriptContext* entryFunctionContext, RecyclableObject *thisObj, JavascriptString *input, bool noResult, void *const stackAllocationPointer) { @@ -2251,27 +2260,28 @@ namespace Js return RegexHelper::CheckCrossContextAndMarshalResult(result, entryFunctionContext); } - Var RegexHelper::RegexExecResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) - { - return RegexHelper::RegexExec(scriptContext, regularExpression, input, false); - } - - Var RegexHelper::RegexExecResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) - { - return RegexHelper::RegexExec(scriptContext, regularExpression, input, false, stackAllocationPointer); - } - - Var RegexHelper::RegexExecResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) - { - if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) - { - return RegexHelper::RegexExec(scriptContext, regularExpression, input, true); - } - else - { - return RegexHelper::RegexExec(scriptContext, regularExpression, input, false); - } - } + // TODO: Cleanup + // Var RegexHelper::RegexExecResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) + // { + // return RegexHelper::RegexExec(scriptContext, regularExpression, input, false); + // } + + // Var RegexHelper::RegexExecResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) + // { + // return RegexHelper::RegexExec(scriptContext, regularExpression, input, false, stackAllocationPointer); + // } + + // Var RegexHelper::RegexExecResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input) + // { + // if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) + // { + // return RegexHelper::RegexExec(scriptContext, regularExpression, input, true); + // } + // else + // { + // return RegexHelper::RegexExec(scriptContext, regularExpression, input, false); + // } + // } Var RegexHelper::RegexExec(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, bool noResult, void *const stackAllocationPointer) { @@ -2279,29 +2289,29 @@ namespace Js return RegexHelper::CheckCrossContextAndMarshalResult(result, entryFunctionContext); } - Var RegexHelper::RegexReplaceResultUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace) - { - return entryFunctionContext->GetConfig()->IsES6RegExSymbolsEnabled() - ? RegexHelper::RegexReplace(entryFunctionContext, regularExpression, input, replace, false) - : RegexHelper::RegexEs5Replace(entryFunctionContext, regularExpression, input, replace, false); - } - - Var RegexHelper::RegexReplaceResultNotUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace) - { - if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) - { - return entryFunctionContext->GetConfig()->IsES6RegExSymbolsEnabled() - ? RegexHelper::RegexReplace(entryFunctionContext, regularExpression, input, replace, true) - : RegexHelper::RegexEs5Replace(entryFunctionContext, regularExpression, input, replace, true); - } - else - { - return entryFunctionContext->GetConfig()->IsES6RegExSymbolsEnabled() - ? RegexHelper::RegexReplace(entryFunctionContext, regularExpression, input, replace, false) - : RegexHelper::RegexEs5Replace(entryFunctionContext, regularExpression, input, replace, false); - } - - } + // Var RegexHelper::RegexReplaceResultUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace) + // { + // return entryFunctionContext->GetConfig()->IsES6RegExSymbolsEnabled() + // ? RegexHelper::RegexReplace(entryFunctionContext, regularExpression, input, replace, false) + // : RegexHelper::RegexEs5Replace(entryFunctionContext, regularExpression, input, replace, false); + // } + + // Var RegexHelper::RegexReplaceResultNotUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace) + // { + // if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) + // { + // return entryFunctionContext->GetConfig()->IsES6RegExSymbolsEnabled() + // ? RegexHelper::RegexReplace(entryFunctionContext, regularExpression, input, replace, true) + // : RegexHelper::RegexEs5Replace(entryFunctionContext, regularExpression, input, replace, true); + // } + // else + // { + // return entryFunctionContext->GetConfig()->IsES6RegExSymbolsEnabled() + // ? RegexHelper::RegexReplace(entryFunctionContext, regularExpression, input, replace, false) + // : RegexHelper::RegexEs5Replace(entryFunctionContext, regularExpression, input, replace, false); + // } + + // } Var RegexHelper::RegexReplace(ScriptContext* entryFunctionContext, RecyclableObject* thisObj, JavascriptString* input, JavascriptString* replace, bool noResult) { @@ -2332,33 +2342,34 @@ namespace Js return RegexHelper::CheckCrossContextAndMarshalResult(result, entryFunctionContext); } - Var RegexHelper::RegexSplitResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit) - { - return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, false); - } + // TODO: Cleanup + // Var RegexHelper::RegexSplitResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit) + // { + // return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, false); + // } - Var RegexHelper::RegexSplitResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit) - { - Assert(ThreadContext::IsOnStack(stackAllocationPointer)); + // Var RegexHelper::RegexSplitResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit) + // { + // Assert(ThreadContext::IsOnStack(stackAllocationPointer)); - return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, false, stackAllocationPointer); - } + // return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, false, stackAllocationPointer); + // } - Var RegexHelper::RegexSplitResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit) - { - if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) - { - return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, true); - } - else - { - return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, false); - } - } + // Var RegexHelper::RegexSplitResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit) + // { + // if (!PHASE_OFF1(Js::RegexResultNotUsedPhase)) + // { + // return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, true); + // } + // else + // { + // return RegexHelper::RegexSplit(scriptContext, regularExpression, input, limit, false); + // } + // } - Var RegexHelper::RegexSplit(ScriptContext* entryFunctionContext, RecyclableObject* thisObj, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer) + Var RegexHelper::RegexSplit(ScriptContext* entryFunctionContext, RecyclableObject* thisObj, JavascriptString* input, Arguments& args, bool noResult, void *const stackAllocationPointer) { - Var result = RegexHelper::RegexSplitImpl(entryFunctionContext, thisObj, input, limit, noResult, stackAllocationPointer); + Var result = RegexHelper::RegexSplitImpl(entryFunctionContext, thisObj, input, args, noResult, stackAllocationPointer); return RegexHelper::CheckCrossContextAndMarshalResult(result, entryFunctionContext); } @@ -2387,18 +2398,39 @@ namespace Js { if (matchStr->GetLength() == 0) { - CharCount lastIndex = JavascriptRegExp::GetLastIndexProperty(instance, scriptContext); + + int64 lastIndex = JavascriptConversion::ToLength( + JavascriptOperators::GetProperty(instance, PropertyIds::lastIndex, scriptContext), + scriptContext); lastIndex = AdvanceStringIndex(input, lastIndex, unicode); - JavascriptRegExp::SetLastIndexProperty(instance, lastIndex, scriptContext); + JavascriptRegExp::SetLastIndexProperty( + instance, + JavascriptNumber::ToVar(lastIndex, scriptContext), + scriptContext); } } - CharCount RegexHelper::AdvanceStringIndex(JavascriptString* string, CharCount index, bool isUnicode) + int64_t RegexHelper::AdvanceStringIndex(JavascriptString* string, int64_t index, bool isUnicode) { - // TODO: Change the increment to 2 depending on the "unicode" flag and - // the code point at "index". The increment is currently constant at 1 - // in order to be compatible with the rest of the RegExp code. + if (isUnicode && string->GetLength() > (0 > index || (uint64_t)index + 1ui64 > 0xffffffffui64 ? UINT32_MAX : (uint32_t)(index + 1)) && + NumberUtilities::IsSurrogateLowerPart(string->GetString()[index]) && + NumberUtilities::IsSurrogateUpperPart(string->GetString()[index + 1])) + { + return index + 2; + } + + return index + 1; + } + CharCount RegexHelper::AdvanceStringIndex(JavascriptString* string, CharCount index, bool isUnicode) + { + if (isUnicode && string->GetLength() > index + 1 && + NumberUtilities::IsSurrogateLowerPart(string->GetString()[index]) && + NumberUtilities::IsSurrogateUpperPart(string->GetString()[index + 1])) + { + return JavascriptRegExp::AddIndex(index, 2); + } + return JavascriptRegExp::AddIndex(index, 1); } } diff --git a/lib/Runtime/Library/RegexHelper.h b/lib/Runtime/Library/RegexHelper.h index 8ab40bb6f0c..7b60e7a8346 100644 --- a/lib/Runtime/Library/RegexHelper.h +++ b/lib/Runtime/Library/RegexHelper.h @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #pragma once @@ -71,14 +72,15 @@ namespace Js // public: - static Var RegexMatchResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); - static Var RegexMatchResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); - static Var RegexMatchResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); + // TODO: Cleanup + // static Var RegexMatchResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); + // static Var RegexMatchResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); + // static Var RegexMatchResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); static Var RegexMatch(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, bool noResult, void *const stackAllocationPointer = nullptr); static Var RegexMatchNoHistory(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, bool noResult); - static Var RegexExecResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); - static Var RegexExecResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); - static Var RegexExecResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); + // static Var RegexExecResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); + // static Var RegexExecResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); + // static Var RegexExecResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); static Var RegexExec(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, bool noResult, void *const stackAllocationPointer = nullptr); static Var RegexTest(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input); template static BOOL RegexTest_NonScript(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, const char16 *const input, const CharCount inputLength); @@ -102,16 +104,17 @@ namespace Js , CompoundString::Builder<64 * sizeof(void *) / sizeof(char16)>& concatenated ); public: - static Var RegexReplaceResultUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace); - static Var RegexReplaceResultNotUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace); + // TODO: Cleanup + // static Var RegexReplaceResultUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace); + // static Var RegexReplaceResultNotUsed(ScriptContext* entryFunctionContext, JavascriptRegExp* regularExpression, JavascriptString* input, JavascriptString* replace); static Var RegexReplace(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, JavascriptString* replace, bool noResult); static Var RegexReplaceFunction(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, RecyclableObject* replacefn); static Var StringReplace(JavascriptString* regularExpression, JavascriptString* input, JavascriptString* replace); static Var StringReplace(ScriptContext* scriptContext, JavascriptString* regularExpression, JavascriptString* input, RecyclableObject* replacefn); - static Var RegexSplitResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit); - static Var RegexSplitResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit); - static Var RegexSplitResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit); - static Var RegexSplit(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer = nullptr); + // static Var RegexSplitResultUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit); + // static Var RegexSplitResultUsedAndMayBeTemp(void *const stackAllocationPointer, ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit); + // static Var RegexSplitResultNotUsed(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit); + static Var RegexSplit(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, Arguments& args, bool noResult, void *const stackAllocationPointer = nullptr); static Var RegexSearch(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); static Var StringSplit(JavascriptString* regularExpression, JavascriptString* input, CharCount limit); static bool IsResultNotUsed(CallFlags flags); @@ -138,8 +141,8 @@ namespace Js static Var RegexSearchImpl(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input); inline static UnifiedRegex::RegexPattern *GetSplitPattern(ScriptContext* scriptContext, JavascriptRegExp *regularExpression); static bool IsRegexSymbolSplitObservable(RecyclableObject* instance, ScriptContext* scriptContext); - static Var RegexSplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer = nullptr); - static Var RegexEs6SplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer = nullptr); + static Var RegexSplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, Arguments& args, bool noResult, void *const stackAllocationPointer = nullptr); + static Var RegexEs6SplitImpl(ScriptContext* scriptContext, RecyclableObject* thisObj, JavascriptString* input, Arguments& args, bool noResult, void *const stackAllocationPointer = nullptr); static JavascriptString* AppendStickyToFlagsIfNeeded(JavascriptString* flags, ScriptContext* scriptContext); static Var RegexEs5SplitImpl(ScriptContext* scriptContext, JavascriptRegExp* regularExpression, JavascriptString* input, CharCount limit, bool noResult, void *const stackAllocationPointer = nullptr); static bool IsRegexTestObservable(RecyclableObject* instance, ScriptContext* scriptContext); @@ -149,6 +152,7 @@ namespace Js static RecyclableObject* ExecResultToRecyclableObject(Var result); static JavascriptString* GetMatchStrFromResult(RecyclableObject* result, ScriptContext* scriptContext); static void AdvanceLastIndex(RecyclableObject* instance, JavascriptString* input, JavascriptString* matchStr, bool unicode, ScriptContext* scriptContext); + static int64_t AdvanceStringIndex(JavascriptString* string, int64_t index, bool isUnicode); static charcount_t AdvanceStringIndex(JavascriptString* string, charcount_t index, bool isUnicode); }; }