diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index b292f3cd3c4..fd3857896eb 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -13625,6 +13625,7 @@ GlobOpt::CheckJsArrayKills(IR::Instr *const instr) case IR::HelperArray_Shift: case IR::HelperArray_Splice: case IR::HelperArray_Unshift: + case IR::HelperArray_Concat: kills.SetKillsArrayHeadSegments(); kills.SetKillsArrayHeadSegmentLengths(); break; @@ -13654,6 +13655,7 @@ GlobOpt::CheckJsArrayKills(IR::Instr *const instr) //case IR::HelperArray_Sort: case IR::HelperArray_Splice: case IR::HelperArray_Unshift: + case IR::HelperArray_Concat: kills.SetKillsNativeArrays(); break; } diff --git a/lib/Backend/GlobOptBailOut.cpp b/lib/Backend/GlobOptBailOut.cpp index f94a7e31acd..829a85de886 100644 --- a/lib/Backend/GlobOptBailOut.cpp +++ b/lib/Backend/GlobOptBailOut.cpp @@ -1324,7 +1324,7 @@ GlobOpt::MayNeedBailOnImplicitCall(IR::Instr const * instr, Value const * src1Va return !( baseValueType.IsString() || - (baseValueType.IsAnyArray() && baseValueType.GetObjectType() != ObjectType::ObjectWithArray) || + baseValueType.IsArray() || (instr->HasBailOutInfo() && instr->GetBailOutKindNoBits() == IR::BailOutOnIrregularLength) // guarantees no implicit calls ); } diff --git a/lib/Backend/GlobOptExpr.cpp b/lib/Backend/GlobOptExpr.cpp index 2af0dbd129a..cc1d4ff0820 100644 --- a/lib/Backend/GlobOptExpr.cpp +++ b/lib/Backend/GlobOptExpr.cpp @@ -844,6 +844,7 @@ GlobOpt::ProcessArrayValueKills(IR::Instr *instr) case IR::HelperArray_Shift: case IR::HelperArray_Unshift: case IR::HelperArray_Splice: + case IR::HelperArray_Concat: this->currentBlock->globOptData.liveArrayValues->ClearAll(); break; } diff --git a/lib/Backend/GlobOptFields.cpp b/lib/Backend/GlobOptFields.cpp index fa2b4f604a6..7939f88d4c0 100644 --- a/lib/Backend/GlobOptFields.cpp +++ b/lib/Backend/GlobOptFields.cpp @@ -1987,20 +1987,8 @@ GlobOpt::UpdateObjPtrValueType(IR::Opnd * opnd, IR::Instr * instr) switch (typeId) { default: - if (typeId > Js::TypeIds_LastStaticType) - { - Assert(typeId != Js::TypeIds_Proxy); - if (objValueType.IsLikelyArrayOrObjectWithArray()) - { - // If we have likely object with array before, we can't make it definite object with array - // since we have only proved that it is an object. - // Keep the likely array or object with array. - } - else - { - newValueType = ValueType::GetObject(ObjectType::Object); - } - } + // Can't mark as definite object because it may actually be object-with-array. + // Consider: a value type that subsumes object, array, and object-with-array. break; case Js::TypeIds_NativeIntArray: case Js::TypeIds_NativeFloatArray: diff --git a/lib/Backend/IRBuilder.cpp b/lib/Backend/IRBuilder.cpp index 4e97a6d22f0..099c05ff3ea 100644 --- a/lib/Backend/IRBuilder.cpp +++ b/lib/Backend/IRBuilder.cpp @@ -3798,7 +3798,7 @@ IRBuilder::BuildElementSlotI1(Js::OpCode newOpcode, uint32 offset, Js::RegSlot r IR::Opnd* IRBuilder::GetEnvironmentOperand(uint32 offset) { - SymID symID; + StackSym* sym = nullptr; // The byte code doesn't refer directly to a closure environment. Get the implicit one // that's pointed to by the function body. if (m_func->DoStackFrameDisplay() && m_func->GetLocalFrameDisplaySym()) @@ -3809,19 +3809,35 @@ IRBuilder::GetEnvironmentOperand(uint32 offset) this->AddInstr( IR::Instr::New(Js::OpCode::LdSlotArr, regOpnd, fieldOpnd, m_func), offset); - symID = regOpnd->m_sym->m_id; + sym = regOpnd->m_sym; } else { + SymID symID; symID = this->GetEnvRegForInnerFrameDisplay(); Assert(symID != Js::Constants::NoRegister); if (IsLoopBody() && !RegIsConstant(symID)) { this->EnsureLoopBodyLoadSlot(symID); } + + if (m_func->DoStackNestedFunc() && symID == GetEnvReg()) + { + // Environment is not guaranteed constant during this function because it could become boxed during execution, + // so load the environment every time you need it. + IR::RegOpnd *regOpnd = IR::RegOpnd::New(TyVar, m_func); + this->AddInstr( + IR::Instr::New(Js::OpCode::LdEnv, regOpnd, m_func), + offset); + sym = regOpnd->m_sym; + } + else + { + sym = StackSym::FindOrCreate(symID, (Js::RegSlot)symID, m_func); + } } - return IR::RegOpnd::New(StackSym::FindOrCreate(symID, (Js::RegSlot)symID, m_func), TyVar, m_func); + return IR::RegOpnd::New(sym, TyVar, m_func); } template diff --git a/lib/Backend/Inline.cpp b/lib/Backend/Inline.cpp index 0b65c7ceb9f..5e774388cf0 100644 --- a/lib/Backend/Inline.cpp +++ b/lib/Backend/Inline.cpp @@ -5294,6 +5294,10 @@ Inline::MapFormals(Func *inlinee, else { instr->SetSrc1(funcObjOpnd); + + // This usage doesn't correspond with any byte code register, since interpreter stack frames + // get their function reference via this->function rather than from a register. + instr->GetSrc1()->SetIsJITOptimizedReg(true); } } else diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index cb32127d3af..045c43682f6 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -3757,22 +3757,6 @@ Lowerer::GenerateProfiledNewScArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteI IR::RegOpnd *headOpnd; uint32 i = length; - auto fillMissingItems = [&](IRType type, uint missingItemCount, uint offsetStart, uint itemSpacing) - { - IR::Opnd * missingItemOpnd = GetMissingItemOpnd(type, func); -#if _M_ARM32_OR_ARM64 - IR::Instr * move = this->InsertMove(IR::RegOpnd::New(type, instr->m_func), missingItemOpnd, instr); - missingItemOpnd = move->GetDst(); -#endif - const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func); - const IR::AutoReuseOpnd autoReuseMissingItemOpnd(missingItemOpnd, func); - - for (; i < missingItemCount; i++) - { - GenerateMemInit(headOpnd, offsetStart + i * itemSpacing, missingItemOpnd, instr, isZeroed); - } - }; - if (instr->GetDst() && instr->GetDst()->GetValueType().IsLikelyNativeIntArray()) { if (!IsSmallObject(length)) @@ -3782,10 +3766,14 @@ Lowerer::GenerateProfiledNewScArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteI GenerateArrayInfoIsNativeIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel); Assert(Js::JavascriptNativeIntArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeIntArray::GetOffsetOfArrayCallSiteIndex()); headOpnd = GenerateArrayLiteralsAlloc(instr, &size, arrayInfo, &isZeroed); + const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func); GenerateMemInit(dstOpnd, Js::JavascriptNativeIntArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, isZeroed); - - fillMissingItems(TyInt32, size, sizeof(Js::SparseArraySegmentBase), sizeof(int32)); + for (; i < size; i++) + { + GenerateMemInit(headOpnd, sizeof(Js::SparseArraySegmentBase) + i * sizeof(int32), + Js::JavascriptNativeIntArray::MissingItem, instr, isZeroed); + } } else if (instr->GetDst() && instr->GetDst()->GetValueType().IsLikelyNativeFloatArray()) { @@ -3796,14 +3784,18 @@ Lowerer::GenerateProfiledNewScArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteI GenerateArrayInfoIsNativeFloatAndNotIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel); Assert(Js::JavascriptNativeFloatArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeFloatArray::GetOffsetOfArrayCallSiteIndex()); headOpnd = GenerateArrayLiteralsAlloc(instr, &size, arrayInfo, &isZeroed); + const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func); GenerateMemInit(dstOpnd, Js::JavascriptNativeFloatArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, isZeroed); - + // Js::JavascriptArray::MissingItem is a Var, so it may be 32-bit or 64 bit. uint const offsetStart = sizeof(Js::SparseArraySegmentBase); - uint const missingItemCount = size * sizeof(double) / sizeof(Js::JavascriptArray::MissingItem); - i = i * sizeof(double) / sizeof(Js::JavascriptArray::MissingItem); - - fillMissingItems(TyVar, missingItemCount, offsetStart, sizeof(Js::JavascriptArray::MissingItem)); + for (; i < size; i++) + { + GenerateMemInit( + headOpnd, offsetStart + i * sizeof(double), + GetMissingItemOpndForAssignment(TyFloat64, m_func), + instr, isZeroed); + } } else { @@ -3811,9 +3803,16 @@ Lowerer::GenerateProfiledNewScArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteI { return false; } - + uint const offsetStart = sizeof(Js::SparseArraySegmentBase); headOpnd = GenerateArrayLiteralsAlloc(instr, &size, arrayInfo, &isZeroed); - fillMissingItems(TyVar, size, sizeof(Js::SparseArraySegmentBase), sizeof(Js::Var)); + const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func); + for (; i < size; i++) + { + GenerateMemInit( + headOpnd, offsetStart + i * sizeof(Js::Var), + GetMissingItemOpndForAssignment(TyVar, m_func), + instr, isZeroed); + } } // Skip pass the helper call @@ -4134,12 +4133,11 @@ Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSi // Js::JavascriptArray::MissingItem is a Var, so it may be 32-bit or 64 bit. uint const offsetStart = sizeof(Js::SparseArraySegmentBase); - uint const missingItemCount = size * sizeof(double) / sizeof(Js::JavascriptArray::MissingItem); - for (uint i = 0; i < missingItemCount; i++) + for (uint i = 0; i < size; i++) { GenerateMemInit( - headOpnd, offsetStart + i * sizeof(Js::JavascriptArray::MissingItem), - IR::AddrOpnd::New(Js::JavascriptArray::MissingItem, IR::AddrOpndKindConstantAddress, m_func, true), + headOpnd, offsetStart + i * sizeof(double), + GetMissingItemOpndForAssignment(TyFloat64, m_func), instr, isZeroed); } } @@ -4149,9 +4147,9 @@ Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSi headOpnd = GenerateArrayObjectsAlloc(instr, &size, arrayInfo, &isZeroed, isNoArgs); for (uint i = 0; i < size; i++) { - GenerateMemInit( + GenerateMemInit( headOpnd, offsetStart + i * sizeof(Js::Var), - IR::AddrOpnd::New(Js::JavascriptArray::MissingItem, IR::AddrOpndKindConstantAddress, m_func, true), + GetMissingItemOpndForAssignment(TyVar, m_func), instr, isZeroed); } } @@ -4182,8 +4180,8 @@ Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSi uint allocationBucketsCount = ArrayType::AllocationBucketsCount; uint(*allocationBuckets)[Js::JavascriptArray::AllocationBucketsInfoSize]; allocationBuckets = ArrayType::allocationBuckets; - uint sizeFactor = 1; - IRType missingItemType = (arrayInfo && arrayInfo->IsNativeIntArray()) ? IRType::TyInt32 : IRType::TyVar; + + IRType missingItemType = (arrayInfo ? arrayInfo->IsNativeIntArray() ? IRType::TyInt32 : arrayInfo->IsNativeFloatArray() ? IRType::TyFloat64 : IRType::TyVar : IRType::TyVar); IR::LabelInstr * arrayInitDone = IR::LabelInstr::New(Js::OpCode::Label, func); bool isNativeArray = arrayInfo && (arrayInfo->IsNativeIntArray() || arrayInfo->IsNativeFloatArray()); @@ -4195,9 +4193,7 @@ Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSi } else if (arrayInfo && arrayInfo->IsNativeFloatArray()) { - // Js::JavascriptArray::MissingItem is a Var, so it may be 32-bit or 64 bit. - sizeFactor = sizeof(double) / sizeof(Js::JavascriptArray::MissingItem); - sizeOfElement = sizeof(Js::JavascriptArray::MissingItem); + sizeOfElement = sizeof(double); GenerateArrayInfoIsNativeFloatAndNotIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel); } else @@ -4227,7 +4223,7 @@ Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSi for (uint8 i = 0;i < allocationBucketsCount;i++) { - missingItemCount = allocationBuckets[i][Js::JavascriptArray::MissingElementsCountIndex] * sizeFactor; + missingItemCount = allocationBuckets[i][Js::JavascriptArray::MissingElementsCountIndex]; if (i > 0) { @@ -4258,7 +4254,7 @@ Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSi // Ensure no. of missingItems written are same Assert(missingItemIndex == missingItemInitializedSoFar); // Ensure no. of missingItems match what present in allocationBuckets - Assert(missingItemIndex == allocationBuckets[allocationBucketsCount - 1][Js::JavascriptArray::MissingElementsCountIndex] * sizeFactor); + Assert(missingItemIndex == allocationBuckets[allocationBucketsCount - 1][Js::JavascriptArray::MissingElementsCountIndex]); instr->InsertBefore(arrayInitDone); @@ -4386,11 +4382,11 @@ Lowerer::GenerateProfiledNewScFloatArrayFastPath(IR::Instr *instr, Js::ArrayCall // Js::JavascriptArray::MissingItem is a Var, so it may be 32-bit or 64 bit. uint const offsetStart = sizeof(Js::SparseArraySegmentBase) + doubles->count * sizeof(double); - uint const missingItem = (size - doubles->count) * sizeof(double) / sizeof(Js::JavascriptArray::MissingItem); + uint const missingItem = (size - doubles->count); for (uint i = 0; i < missingItem; i++) { - GenerateMemInit(headOpnd, offsetStart + i * sizeof(Js::JavascriptArray::MissingItem), - IR::AddrOpnd::New(Js::JavascriptArray::MissingItem, IR::AddrOpndKindConstantAddress, m_func, true), instr, isHeadSegmentZeroed); + GenerateMemInit(headOpnd, offsetStart + i * sizeof(double), + GetMissingItemOpndForAssignment(TyFloat64, m_func), instr, isHeadSegmentZeroed); } // Skip pass the helper call IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, func); diff --git a/lib/Parser/Scan.cpp b/lib/Parser/Scan.cpp index 586c073178e..9d4e9412a36 100644 --- a/lib/Parser/Scan.cpp +++ b/lib/Parser/Scan.cpp @@ -193,13 +193,16 @@ void Scanner::PrepareForBackgroundParse(Js::ScriptContext *scrip // This is used to determine a length of BSTR, which can't contain a NUL character. //----------------------------------------------------------------------------- template -charcount_t Scanner::LineLength(EncodedCharPtr first, EncodedCharPtr last) +charcount_t Scanner::LineLength(EncodedCharPtr first, EncodedCharPtr last, size_t* cb) { + Assert(cb != nullptr); + charcount_t result = 0; EncodedCharPtr p = first; for (;;) { + EncodedCharPtr prev = p; switch( this->template ReadFull(p, last) ) { case kchNWL: // _C_NWL @@ -207,6 +210,13 @@ charcount_t Scanner::LineLength(EncodedCharPtr first, EncodedCha case kchLS: case kchPS: case kchNUL: // _C_NUL + // p is now advanced past the line terminator character. + // We need to know the number of bytes making up the line, not including the line terminator character. + // To avoid subtracting a variable number of bytes because the line terminator characters are different + // number of bytes long (plus there may be multiple valid encodings for these characters) just keep + // track of the first byte of the line terminator character in prev. + Assert(prev >= first); + *cb = prev - first; return result; } result++; @@ -2331,10 +2341,11 @@ HRESULT Scanner::SysAllocErrorLine(int32 ichMinLine, __out BSTR* typename EncodingPolicy::EncodedCharPtr pStart = static_cast(ichMinLine) == IchMinLine() ? m_pchMinLine : m_pchBase + this->CharacterOffsetToUnitOffset(m_pchBase, m_currentCharacter, m_pchLast, ichMinLine); // Determine the length by scanning for the next newline - charcount_t cch = LineLength(pStart, m_pchLast); + size_t cb = 0; + charcount_t cch = LineLength(pStart, m_pchLast, &cb); Assert(cch <= LONG_MAX); - typename EncodingPolicy::EncodedCharPtr pEnd = static_cast(ichMinLine) == IchMinLine() ? m_pchMinLine + cch : m_pchBase + this->CharacterOffsetToUnitOffset(m_pchBase, m_currentCharacter, m_pchLast, cch); + typename EncodingPolicy::EncodedCharPtr pEnd = static_cast(ichMinLine) == IchMinLine() ? m_pchMinLine + cb : m_pchBase + this->CharacterOffsetToUnitOffset(m_pchBase, m_currentCharacter, m_pchLast, cch); *pbstrLine = SysAllocStringLen(NULL, cch); if (!*pbstrLine) diff --git a/lib/Parser/Scan.h b/lib/Parser/Scan.h index c4bb3faeef7..4f2c481979e 100644 --- a/lib/Parser/Scan.h +++ b/lib/Parser/Scan.h @@ -793,7 +793,7 @@ class Scanner : public IScanner, public EncodingPolicy void ScanNewLine(uint ch); void NotifyScannedNewLine(); - charcount_t LineLength(EncodedCharPtr first, EncodedCharPtr last); + charcount_t LineLength(EncodedCharPtr first, EncodedCharPtr last, size_t* cb); tokens ScanIdentifier(bool identifyKwds, EncodedCharPtr *pp); BOOL FastIdentifierContinue(EncodedCharPtr&p, EncodedCharPtr last); diff --git a/lib/Runtime/Language/InterpreterStackFrame.cpp b/lib/Runtime/Language/InterpreterStackFrame.cpp index f85396553e6..a6b0a24feb6 100644 --- a/lib/Runtime/Language/InterpreterStackFrame.cpp +++ b/lib/Runtime/Language/InterpreterStackFrame.cpp @@ -6793,6 +6793,12 @@ namespace Js // Finally exited with LeaveNull, We don't throw for early returns if (finallyEndOffset == 0 && exceptionObj) { +#if ENABLE_NATIVE_CODEGEN + if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) + { + JavascriptExceptionOperators::WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); + } +#endif JavascriptExceptionOperators::DoThrow(const_cast(exceptionObj), scriptContext); } if (finallyEndOffset != 0) diff --git a/lib/Runtime/Language/JavascriptExceptionOperators.cpp b/lib/Runtime/Language/JavascriptExceptionOperators.cpp index b3f60e3dbc0..5bb2fb808ea 100644 --- a/lib/Runtime/Language/JavascriptExceptionOperators.cpp +++ b/lib/Runtime/Language/JavascriptExceptionOperators.cpp @@ -190,19 +190,23 @@ namespace Js { void *tryContinuation = nullptr; JavascriptExceptionObject *exception = nullptr; + void *tryHandlerAddrOfReturnAddr = nullptr; Js::JavascriptExceptionOperators::HasBailedOutPtrStack hasBailedOutPtrStack(scriptContext, (bool*)((char*)frame + hasBailedOutOffset)); PROBE_STACK(scriptContext, Constants::MinStackJitEHBailout + spillSize + argsSize); - - try - { - tryContinuation = amd64_CallWithFakeFrame(tryAddr, frame, spillSize, argsSize); - } - catch (const Js::JavascriptException& err) { - exception = err.GetAndClear(); + void * addrOfReturnAddr = (void*)((char*)frame + sizeof(char*)); + Js::JavascriptExceptionOperators::TryHandlerAddrOfReturnAddrStack tryHandlerAddrOfReturnAddrStack(scriptContext, addrOfReturnAddr); + try + { + tryContinuation = amd64_CallWithFakeFrame(tryAddr, frame, spillSize, argsSize); + } + catch (const Js::JavascriptException& err) + { + exception = err.GetAndClear(); + tryHandlerAddrOfReturnAddr = scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr(); + } } - if (exception) { // Clone static exception object early in case finally block overwrites it @@ -212,19 +216,9 @@ namespace Js if (exception) { #if ENABLE_NATIVE_CODEGEN - if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) - { - if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) - { - WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); - } - } - else + if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) { - if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) - { - WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, frame); - } + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, tryHandlerAddrOfReturnAddr); } #endif bool hasBailedOut = *(bool*)((char*)frame + hasBailedOutOffset); // stack offsets are negative @@ -251,21 +245,32 @@ namespace Js void *tryContinuation = nullptr; void *finallyContinuation = nullptr; JavascriptExceptionObject *exception = nullptr; + void *tryHandlerAddrOfReturnAddr = nullptr; PROBE_STACK(scriptContext, Constants::MinStackJitEHBailout + spillSize + argsSize); - try - { - tryContinuation = amd64_CallWithFakeFrame(tryAddr, frame, spillSize, argsSize); - } - catch (const Js::JavascriptException& err) { - exception = err.GetAndClear(); + void * addrOfReturnAddr = (void*)((char*)frame + sizeof(char*)); + Js::JavascriptExceptionOperators::TryHandlerAddrOfReturnAddrStack tryHandlerAddrOfReturnAddrStack(scriptContext, addrOfReturnAddr); + try + { + tryContinuation = amd64_CallWithFakeFrame(tryAddr, frame, spillSize, argsSize); + } + catch (const Js::JavascriptException& err) + { + exception = err.GetAndClear(); + tryHandlerAddrOfReturnAddr = scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr(); + } } - if (exception) { // Clone static exception object early in case finally block overwrites it exception = exception->CloneIfStaticExceptionObject(scriptContext); +#if ENABLE_NATIVE_CODEGEN + if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) + { + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, tryHandlerAddrOfReturnAddr); + } +#endif } finallyContinuation = amd64_CallWithFakeFrame(finallyAddr, frame, spillSize, argsSize); @@ -276,6 +281,12 @@ namespace Js if (exception) { +#if ENABLE_NATIVE_CODEGEN + if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) + { + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); + } +#endif JavascriptExceptionOperators::DoThrow(exception, scriptContext); } @@ -365,44 +376,40 @@ namespace Js int hasBailedOutOffset, ScriptContext *scriptContext) { - void *tryContinuation = nullptr; - JavascriptExceptionObject *exception = nullptr; + void *tryContinuation = nullptr; + JavascriptExceptionObject *exception = nullptr; + void *tryHandlerAddrOfReturnAddr = nullptr; + Js::JavascriptExceptionOperators::HasBailedOutPtrStack hasBailedOutPtrStack(scriptContext, (bool*)((char*)localsPtr + hasBailedOutOffset)); PROBE_STACK(scriptContext, Constants::MinStackJitEHBailout + argsSize); - try { + void * addrOfReturnAddr = (void*)((char*)framePtr + sizeof(char*)); + Js::JavascriptExceptionOperators::TryHandlerAddrOfReturnAddrStack tryHandlerAddrOfReturnAddrStack(scriptContext, addrOfReturnAddr); + try + { #if defined(_M_ARM) tryContinuation = arm_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize); #elif defined(_M_ARM64) tryContinuation = arm64_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize); #endif + } + catch (const Js::JavascriptException& err) + { + exception = err.GetAndClear(); + tryHandlerAddrOfReturnAddr = scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr(); + } } - catch (const Js::JavascriptException& err) - { - exception = err.GetAndClear(); - } - if (exception) { + // Clone static exception object early in case finally block overwrites it + exception = exception->CloneIfStaticExceptionObject(scriptContext); #if ENABLE_NATIVE_CODEGEN - if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) - { - if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) - { - WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); - } - } - else + if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) { - if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) - { - WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, framePtr); - } + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, tryHandlerAddrOfReturnAddr); } #endif - // Clone static exception object early in case finally block overwrites it - exception = exception->CloneIfStaticExceptionObject(scriptContext); bool hasBailedOut = *(bool*)((char*)localsPtr + hasBailedOutOffset); // stack offsets are sp relative if (hasBailedOut) { @@ -437,26 +444,38 @@ namespace Js void *tryContinuation = nullptr; void *finallyContinuation = nullptr; JavascriptExceptionObject *exception = nullptr; + void *tryHandlerAddrOfReturnAddr = nullptr; PROBE_STACK(scriptContext, Constants::MinStackJitEHBailout + argsSize); - - try { + void * addrOfReturnAddr = (void*)((char*)framePtr + sizeof(char*)); + Js::JavascriptExceptionOperators::TryHandlerAddrOfReturnAddrStack tryHandlerAddrOfReturnAddrStack(scriptContext, addrOfReturnAddr); + + try + { #if defined(_M_ARM) - tryContinuation = arm_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize); + tryContinuation = arm_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize); #elif defined(_M_ARM64) - tryContinuation = arm64_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize); + tryContinuation = arm64_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize); #endif + } + catch (const Js::JavascriptException& err) + { + exception = err.GetAndClear(); + tryHandlerAddrOfReturnAddr = scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr(); + } } - catch (const Js::JavascriptException& err) - { - exception = err.GetAndClear(); - } - if (exception) { // Clone static exception object early in case finally block overwrites it exception = exception->CloneIfStaticExceptionObject(scriptContext); + +#if ENABLE_NATIVE_CODEGEN + if (exception->GetExceptionContext() && exception->GetExceptionContext()->ThrowingFunction()) + { + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, tryHandlerAddrOfReturnAddr); + } +#endif } #if defined(_M_ARM) @@ -472,6 +491,12 @@ namespace Js if (exception) { +#if ENABLE_NATIVE_CODEGEN + if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) + { + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); + } +#endif JavascriptExceptionOperators::DoThrow(exception, scriptContext); } @@ -647,21 +672,24 @@ namespace Js { Js::JavascriptExceptionObject* pExceptionObject = NULL; void* continuationAddr = NULL; + void* tryHandlerAddrOfReturnAddr = nullptr; Js::JavascriptExceptionOperators::HasBailedOutPtrStack hasBailedOutPtrStack(scriptContext, (bool*)((char*)framePtr + hasBailedOutOffset)); PROBE_STACK(scriptContext, Constants::MinStackJitEHBailout); - - try { - // Bug in compiler optimizer: try-catch can be optimized away if the try block contains __asm calls into function - // that may throw. The current workaround is to add the following dummy throw to prevent this optimization. - // It seems like compiler got smart and still optimizes if the exception is not JavascriptExceptionObject (see catch handler below). - // In order to circumvent that we are throwing OutOfMemory. - if (!tryAddr) + void * addrOfReturnAddr = (void*)((char*)framePtr + sizeof(char*)); + Js::JavascriptExceptionOperators::TryHandlerAddrOfReturnAddrStack tryHandlerAddrOfReturnAddrStack(scriptContext, addrOfReturnAddr); + try { - Assert(false); - ThrowOutOfMemory(scriptContext); - } + // Bug in compiler optimizer: try-catch can be optimized away if the try block contains __asm calls into function + // that may throw. The current workaround is to add the following dummy throw to prevent this optimization. + // It seems like compiler got smart and still optimizes if the exception is not JavascriptExceptionObject (see catch handler below). + // In order to circumvent that we are throwing OutOfMemory. + if (!tryAddr) + { + Assert(false); + ThrowOutOfMemory(scriptContext); + } #ifdef _M_IX86 void *savedEsp; @@ -711,28 +739,19 @@ namespace Js #else AssertMsg(FALSE, "Unsupported native try-finally handler"); #endif + } + catch (const Js::JavascriptException& err) + { + pExceptionObject = err.GetAndClear(); + tryHandlerAddrOfReturnAddr = scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr(); + } } - catch(const Js::JavascriptException& err) - { - pExceptionObject = err.GetAndClear(); - } - if (pExceptionObject) { #if ENABLE_NATIVE_CODEGEN - if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) - { - if (pExceptionObject->GetExceptionContext() && pExceptionObject->GetExceptionContext()->ThrowingFunction()) - { - WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); - } - } - else + if (pExceptionObject->GetExceptionContext() && pExceptionObject->GetExceptionContext()->ThrowingFunction()) { - if (pExceptionObject->GetExceptionContext() && pExceptionObject->GetExceptionContext()->ThrowingFunction()) - { - WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, framePtr); - } + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, tryHandlerAddrOfReturnAddr); } #endif // Clone static exception object early in case finally block overwrites it @@ -817,79 +836,91 @@ namespace Js { Js::JavascriptExceptionObject* pExceptionObject = NULL; void* continuationAddr = NULL; + void * tryHandlerAddrOfReturnAddr = nullptr; PROBE_STACK(scriptContext, Constants::MinStackJitEHBailout); - - try { - // Bug in compiler optimizer: try-catch can be optimized away if the try block contains __asm calls into function - // that may throw. The current workaround is to add the following dummy throw to prevent this optimization. - // It seems like compiler got smart and still optimizes if the exception is not JavascriptExceptionObject (see catch handler below). - // In order to circumvent that we are throwing OutOfMemory. - if (!tryAddr) + void * addrOfReturnAddr = (void*)((char*)framePtr + sizeof(char*)); + Js::JavascriptExceptionOperators::TryHandlerAddrOfReturnAddrStack tryHandlerAddrOfReturnAddrStack(scriptContext, addrOfReturnAddr); + + try { - Assert(false); - ThrowOutOfMemory(scriptContext); - } + // Bug in compiler optimizer: try-catch can be optimized away if the try block contains __asm calls into function + // that may throw. The current workaround is to add the following dummy throw to prevent this optimization. + // It seems like compiler got smart and still optimizes if the exception is not JavascriptExceptionObject (see catch handler below). + // In order to circumvent that we are throwing OutOfMemory. + if (!tryAddr) + { + Assert(false); + ThrowOutOfMemory(scriptContext); + } #ifdef _M_IX86 - void *savedEsp; - __asm - { - // Save and restore the callee-saved registers around the call. - // TODO: track register kills by region and generate per-region prologs and epilogs - push esi - push edi - push ebx + void *savedEsp; + __asm + { + // Save and restore the callee-saved registers around the call. + // TODO: track register kills by region and generate per-region prologs and epilogs + push esi + push edi + push ebx - // 8-byte align frame to improve floating point perf of our JIT'd code. - // Save ESP - mov ecx, esp - mov savedEsp, ecx - and esp, -8 + // 8-byte align frame to improve floating point perf of our JIT'd code. + // Save ESP + mov ecx, esp + mov savedEsp, ecx + and esp, -8 - // Set up the call target, save the current frame ptr, and adjust the frame to access - // locals in native code. - mov eax, tryAddr + // Set up the call target, save the current frame ptr, and adjust the frame to access + // locals in native code. + mov eax, tryAddr #if 0 && defined(_CONTROL_FLOW_GUARD) - // verify that the call target is valid - mov ebx, eax; save call target - mov ecx, eax - call[__guard_check_icall_fptr] - mov eax, ebx; restore call target + // verify that the call target is valid + mov ebx, eax; save call target + mov ecx, eax + call[__guard_check_icall_fptr] + mov eax, ebx; restore call target #endif - push ebp - mov ebp, framePtr - call eax - pop ebp + push ebp + mov ebp, framePtr + call eax + pop ebp - // The native code gives us the address where execution should continue on exit - // from the region. - mov continuationAddr, eax + // The native code gives us the address where execution should continue on exit + // from the region. + mov continuationAddr, eax - // Restore ESP - mov ecx, savedEsp - mov esp, ecx + // Restore ESP + mov ecx, savedEsp + mov esp, ecx - pop ebx - pop edi - pop esi - } + pop ebx + pop edi + pop esi + } #else - AssertMsg(FALSE, "Unsupported native try-finally handler"); + AssertMsg(FALSE, "Unsupported native try-finally handler"); #endif + } + catch (const Js::JavascriptException& err) + { + pExceptionObject = err.GetAndClear(); + tryHandlerAddrOfReturnAddr = scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr(); + } } - catch (const Js::JavascriptException& err) - { - pExceptionObject = err.GetAndClear(); - } - if (pExceptionObject) { // Clone static exception object early in case finally block overwrites it pExceptionObject = pExceptionObject->CloneIfStaticExceptionObject(scriptContext); + +#if ENABLE_NATIVE_CODEGEN + if (pExceptionObject->GetExceptionContext() && pExceptionObject->GetExceptionContext()->ThrowingFunction()) + { + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr /* start stackwalk from the current frame */, tryHandlerAddrOfReturnAddr); + } +#endif } void* newContinuationAddr = NULL; @@ -952,6 +983,12 @@ namespace Js if (pExceptionObject) { +#if ENABLE_NATIVE_CODEGEN + if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) + { + WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); + } +#endif JavascriptExceptionOperators::DoThrow(pExceptionObject, scriptContext); } diff --git a/lib/Runtime/Language/ValueType.cpp b/lib/Runtime/Language/ValueType.cpp index bfdee9e0029..ac295dc85a2 100644 --- a/lib/Runtime/Language/ValueType.cpp +++ b/lib/Runtime/Language/ValueType.cpp @@ -1055,6 +1055,10 @@ ValueType ValueType::MergeWithObject(const ValueType other) const { // Any two different specific object types (excludes UninitializedObject and Object, which don't indicate any // specific type of object) merge to Object since the resulting type is not guaranteed to indicate any specific type + if (IsArrayOrObjectWithArray() || other.IsArrayOrObjectWithArray()) + { + return Verify(GetObject(ObjectType::Object).ToLikely()); + } merged.SetObjectType(ObjectType::Object); return Verify(merged); } @@ -1945,13 +1949,18 @@ void ValueType::RunUnitTests() )); if(!( - t0.IsObject() && t1.IsObject() && // both are objects + t0.IsObject() && t1.IsObject() && // both are objects ( - t0.GetObjectType() == ObjectType::UninitializedObject || - t1.GetObjectType() == ObjectType::UninitializedObject - ) && // one has an uninitialized object type - (t0.GetObjectType() > ObjectType::Object || t1.GetObjectType() > ObjectType::Object) // one has a specific object type - )) // then the resulting object type is not guaranteed + ( + ( + t0.GetObjectType() == ObjectType::UninitializedObject || + t1.GetObjectType() == ObjectType::UninitializedObject + ) && // one has an uninitialized object type + (t0.GetObjectType() > ObjectType::Object || t1.GetObjectType() > ObjectType::Object) // one has a specific object type + ) || + (t0.IsArrayOrObjectWithArray() || t1.IsArrayOrObjectWithArray()) // or one was an array or an object with array + ) + )) // then the resulting object type is not guaranteed { Assert(m.IsNotInt() == (t0.IsNotInt() && t1.IsNotInt())); } @@ -1990,13 +1999,18 @@ void ValueType::RunUnitTests() Assert(m.IsLikelyString() == (t0.IsLikelyString() && t1.IsLikelyString())); if(!( - t0.IsObject() && t1.IsObject() && // both are objects + t0.IsObject() && t1.IsObject() && // both are objects ( - t0.GetObjectType() == ObjectType::UninitializedObject || - t1.GetObjectType() == ObjectType::UninitializedObject - ) && // one has an uninitialized object type - (t0.GetObjectType() > ObjectType::Object || t1.GetObjectType() > ObjectType::Object) // one has a specific object type - )) // then the resulting object type is not guaranteed + ( + ( + t0.GetObjectType() == ObjectType::UninitializedObject || + t1.GetObjectType() == ObjectType::UninitializedObject + ) && // one has an uninitialized object type + (t0.GetObjectType() > ObjectType::Object || t1.GetObjectType() > ObjectType::Object) // one has a specific object type + ) || + (t0.IsArrayOrObjectWithArray() || t1.IsArrayOrObjectWithArray()) // or one was an array or an object with array + ) + )) // then the resulting object type is not guaranteed { Assert(m.IsObject() == (t0.IsObject() && t1.IsObject())); } diff --git a/lib/Runtime/Library/JavascriptArray.cpp b/lib/Runtime/Library/JavascriptArray.cpp index 6572a64c929..02ba0f1967a 100644 --- a/lib/Runtime/Library/JavascriptArray.cpp +++ b/lib/Runtime/Library/JavascriptArray.cpp @@ -28,7 +28,7 @@ using namespace Js; { 8, 0, 0 }, // allocate space for 8 elements for array of length 6,7,8 }; - const Var JavascriptArray::MissingItem = (Var)FloatMissingItemPattern; + const Var JavascriptArray::MissingItem = (Var)VarMissingItemPattern; #if defined(TARGET_64) const Var JavascriptArray::IntMissingItemVar = (Var)(((uint64)IntMissingItemPattern << 32) | (uint32)IntMissingItemPattern); @@ -2039,6 +2039,8 @@ using namespace Js; { ((SparseArraySegment*)seg)->elements[i] = JavascriptNumber::ToVar(ival, scriptContext); } + SparseArraySegment* newSeg = (SparseArraySegment*)seg; + newSeg->FillSegmentBuffer(seg->length, seg->size); } prevSeg = seg; } @@ -2234,7 +2236,7 @@ using namespace Js; } } } - if (seg == newSeg && shrinkFactor != 1) + if (seg == newSeg) { // Fill the remaining slots. newSeg->FillSegmentBuffer(i, seg->size); diff --git a/lib/Runtime/Library/JavascriptArray.inl b/lib/Runtime/Library/JavascriptArray.inl index 0f906e23be1..78de6803fd2 100644 --- a/lib/Runtime/Library/JavascriptArray.inl +++ b/lib/Runtime/Library/JavascriptArray.inl @@ -486,6 +486,8 @@ namespace Js template inline void JavascriptArray::DirectSetItemInLastUsedSegmentAt(const uint32 offset, const T newValue) { + Assert(!SparseArraySegment::IsMissingItem(&newValue)); + SparseArraySegment *const seg = (SparseArraySegment*)GetLastUsedSegment(); Assert(seg); Assert(offset < seg->size); @@ -526,6 +528,8 @@ namespace Js const T newValue, StElemInfo *const stElemInfo) { + Assert(!SparseArraySegment::IsMissingItem(&newValue)); + SparseArraySegment *const seg = SparseArraySegment::From(head); Assert(seg); Assert(offset < seg->size); @@ -1219,6 +1223,8 @@ SECOND_PASS: template void JavascriptArray::DirectSetItem_Full(uint32 itemIndex, T newValue) { + Assert(!SparseArraySegment::IsMissingItem(&newValue)); + DebugOnly(VerifyNotNeedMarshal(newValue)); this->EnsureHead(); AnalysisAssert(head); diff --git a/lib/Runtime/Library/SparseArraySegment.h b/lib/Runtime/Library/SparseArraySegment.h index 219ad532f81..42edbc0fb39 100644 --- a/lib/Runtime/Library/SparseArraySegment.h +++ b/lib/Runtime/Library/SparseArraySegment.h @@ -147,6 +147,7 @@ namespace Js return JavascriptArray::MissingItem; } template<> Var SparseArraySegment::GetMissingItemVar(); + template<> Var SparseArraySegment::GetMissingItemVar(); template<> inline bool SparseArraySegment::IsMissingItem(const double* value) diff --git a/lib/Runtime/Library/SparseArraySegment.inl b/lib/Runtime/Library/SparseArraySegment.inl index 669faea8943..8e0f1515db1 100644 --- a/lib/Runtime/Library/SparseArraySegment.inl +++ b/lib/Runtime/Library/SparseArraySegment.inl @@ -229,6 +229,12 @@ namespace Js return JavascriptArray::IntMissingItemVar; } + template<> + inline Var SparseArraySegment::GetMissingItemVar() + { + return (Var)FloatMissingItemPattern; + } + template void SparseArraySegment::FillSegmentBuffer(uint32 start, uint32 size) { diff --git a/lib/Runtime/RuntimeCommon.h b/lib/Runtime/RuntimeCommon.h index 484953244a8..da1b7aa327a 100644 --- a/lib/Runtime/RuntimeCommon.h +++ b/lib/Runtime/RuntimeCommon.h @@ -179,6 +179,9 @@ namespace Js #if FLOATVAR const uint64 FloatTag_Value = 0xFFFCull << 48; + const uint64 VarMissingItemPattern = 0x00040002FFF80002; // Float-tagged representation of FloatMissingItemPattern +#else + const int32 VarMissingItemPattern = 0xFFF80002; #endif const uint64 FloatMissingItemPattern = 0xFFF80002FFF80002; const int32 IntMissingItemPattern = 0xFFF80002; diff --git a/test/Bugs/bug_5585.js b/test/Bugs/bug_5585.js new file mode 100644 index 00000000000..afa523ec63d --- /dev/null +++ b/test/Bugs/bug_5585.js @@ -0,0 +1,55 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +let line = 't("摩"2)'; +let module_name = 'temp.js'; +WScript.RegisterModuleSource(module_name, line); + +var tests = [ + { + name: "Syntax error thrown parsing dynamic module", + body: function () { + let source = `import(module_name) + .then(v => { + assert.fail("Parsing this module should not succeed"); + }, e => { + assert.areEqual(line, e.source, "Source line causing compile error"); + }).catch(e => { + console.log('fail: ' + e); + throw e; + });` + + testRunner.LoadModule(source, 'samethread', true, false); + } + }, + { + name: "Syntax error thrown parsing module code", + body: function () { + try { + WScript.LoadScriptFile(module_name, 'module'); + assert.fail("Parsing this module should not succeed"); + } catch(e) { + assert.areEqual(line, e.source, "Source line causing compile error"); + } + } + }, + { + name: "Error line which contains multi-byte UTF-8 sequence which is an end-of-line character", + body: function () { + WScript.RegisterModuleSource('temp2.js', 't("\u2028"2)'); + + try { + WScript.LoadScriptFile('temp2.js', 'module'); + assert.fail("Parsing this module should not succeed"); + } catch(e) { + assert.areEqual('t("', e.source, "Source line causing compile error"); + } + } + } +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/Bugs/rlexe.xml b/test/Bugs/rlexe.xml index 6d6138f68a7..88c6019d68d 100644 --- a/test/Bugs/rlexe.xml +++ b/test/Bugs/rlexe.xml @@ -544,4 +544,10 @@ exclude_jshost + + + bug_5585.js + -esdynamicimport -mutehosterrormsg -args summary -endargs + +