diff --git a/lib/Backend/BackwardPass.cpp b/lib/Backend/BackwardPass.cpp index fc32c3aaaed..16426767857 100644 --- a/lib/Backend/BackwardPass.cpp +++ b/lib/Backend/BackwardPass.cpp @@ -6860,10 +6860,7 @@ BackwardPass::TrackNoImplicitCallInlinees(IR::Instr *instr) || OpCodeAttr::CallInstr(instr->m_opcode) || instr->CallsAccessor() || GlobOpt::MayNeedBailOnImplicitCall(instr, nullptr, nullptr) - || instr->m_opcode == Js::OpCode::LdHeapArguments - || instr->m_opcode == Js::OpCode::LdLetHeapArguments - || instr->m_opcode == Js::OpCode::LdHeapArgsCached - || instr->m_opcode == Js::OpCode::LdLetHeapArgsCached + || instr->HasAnyLoadHeapArgsOpCode() || instr->m_opcode == Js::OpCode::LdFuncExpr) { // This func has instrs with bailouts or implicit calls diff --git a/lib/Backend/Func.cpp b/lib/Backend/Func.cpp index d970c63aedf..143fc86da17 100644 --- a/lib/Backend/Func.cpp +++ b/lib/Backend/Func.cpp @@ -95,7 +95,7 @@ Func::Func(JitArenaAllocator *alloc, JITTimeWorkItem * workItem, hasThrow(false), hasNonSimpleParams(false), hasUnoptimizedArgumentsAccess(false), - hasApplyTargetInlining(false), + applyTargetInliningRemovedArgumentsAccess(false), hasImplicitCalls(false), hasTempObjectProducingInstr(false), isInlinedConstructor(isInlinedConstructor), diff --git a/lib/Backend/Func.h b/lib/Backend/Func.h index 5860e2d7e95..ed52d0660ff 100644 --- a/lib/Backend/Func.h +++ b/lib/Backend/Func.h @@ -519,12 +519,24 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; IR::SymOpnd *GetNextInlineeFrameArgCountSlotOpnd() { Assert(!this->m_hasInlineArgsOpt); + if (this->m_hasInlineArgsOpt) + { + // If the function has inlineArgsOpt turned on, jitted code will not write to stack slots for inlinee's function object + // and arguments, until needed. If we attempt to read from those slots, we may be reading uninitialized memory. + throw Js::OperationAbortedException(); + } return GetInlineeOpndAtOffset((Js::Constants::InlineeMetaArgCount + actualCount) * MachPtr); } IR::SymOpnd *GetInlineeFunctionObjectSlotOpnd() { Assert(!this->m_hasInlineArgsOpt); + if (this->m_hasInlineArgsOpt) + { + // If the function has inlineArgsOpt turned on, jitted code will not write to stack slots for inlinee's function object + // and arguments, until needed. If we attempt to read from those slots, we may be reading uninitialized memory. + throw Js::OperationAbortedException(); + } return GetInlineeOpndAtOffset(Js::Constants::InlineeMetaArgIndex_FunctionObject * MachPtr); } @@ -536,6 +548,12 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; IR::SymOpnd *GetInlineeArgvSlotOpnd() { Assert(!this->m_hasInlineArgsOpt); + if (this->m_hasInlineArgsOpt) + { + // If the function has inlineArgsOpt turned on, jitted code will not write to stack slots for inlinee's function object + // and arguments, until needed. If we attempt to read from those slots, we may be reading uninitialized memory. + throw Js::OperationAbortedException(); + } return GetInlineeOpndAtOffset(Js::Constants::InlineeMetaArgIndex_Argv * MachPtr); } @@ -696,7 +714,7 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; bool hasThrow : 1; bool hasUnoptimizedArgumentsAccess : 1; // True if there are any arguments access beyond the simple case of this.apply pattern bool m_canDoInlineArgsOpt : 1; - bool hasApplyTargetInlining:1; + bool applyTargetInliningRemovedArgumentsAccess :1; bool isGetterSetter : 1; const bool isInlinedConstructor: 1; bool hasImplicitCalls: 1; @@ -808,8 +826,8 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; } } - bool GetHasApplyTargetInlining() const { return this->hasApplyTargetInlining;} - void SetHasApplyTargetInlining() { this->hasApplyTargetInlining = true;} + bool GetApplyTargetInliningRemovedArgumentsAccess() const { return this->applyTargetInliningRemovedArgumentsAccess;} + void SetApplyTargetInliningRemovedArgumentsAccess() { this->applyTargetInliningRemovedArgumentsAccess = true;} bool GetHasMarkTempObjects() const { return this->hasMarkTempObjects; } void SetHasMarkTempObjects() { this->hasMarkTempObjects = true; } diff --git a/lib/Backend/GlobOpt.h b/lib/Backend/GlobOpt.h index 8999b467d3f..fde3292fed6 100644 --- a/lib/Backend/GlobOpt.h +++ b/lib/Backend/GlobOpt.h @@ -898,6 +898,7 @@ class GlobOpt bool PreparePropertySymOpndForTypeCheckSeq(IR::PropertySymOpnd *propertySymOpnd, IR::Instr * instr, Loop *loop); static bool AreTypeSetsIdentical(Js::EquivalentTypeSet * leftTypeSet, Js::EquivalentTypeSet * rightTypeSet); static bool IsSubsetOf(Js::EquivalentTypeSet * leftTypeSet, Js::EquivalentTypeSet * rightTypeSet); + static bool CompareCurrentTypesWithExpectedTypes(JsTypeValueInfo *valueInfo, IR::PropertySymOpnd * propertySymOpnd); bool ProcessPropOpInTypeCheckSeq(IR::Instr* instr, IR::PropertySymOpnd *opnd); bool CheckIfInstrInTypeCheckSeqEmitsTypeCheck(IR::Instr* instr, IR::PropertySymOpnd *opnd); template diff --git a/lib/Backend/GlobOptBlockData.cpp b/lib/Backend/GlobOptBlockData.cpp index 657c389defc..41da586c382 100644 --- a/lib/Backend/GlobOptBlockData.cpp +++ b/lib/Backend/GlobOptBlockData.cpp @@ -1439,6 +1439,18 @@ GlobOptBlockData::FindObjectTypeValue(SymID typeSymId) { return nullptr; } + return FindObjectTypeValueNoLivenessCheck(typeSymId); +} + +Value * +GlobOptBlockData::FindObjectTypeValueNoLivenessCheck(StackSym* typeSym) +{ + return FindObjectTypeValueNoLivenessCheck(typeSym->m_id); +} + +Value * +GlobOptBlockData::FindObjectTypeValueNoLivenessCheck(SymID typeSymId) +{ Value* value = this->FindValueFromMapDirect(typeSymId); Assert(value == nullptr || value->GetValueInfo()->IsJsType()); return value; diff --git a/lib/Backend/GlobOptBlockData.h b/lib/Backend/GlobOptBlockData.h index 62fd0da8268..b0070740e7b 100644 --- a/lib/Backend/GlobOptBlockData.h +++ b/lib/Backend/GlobOptBlockData.h @@ -299,6 +299,8 @@ class GlobOptBlockData Value * FindPropertyValue(SymID symId); Value * FindObjectTypeValue(StackSym* typeSym); Value * FindObjectTypeValue(SymID typeSymId); + Value * FindObjectTypeValueNoLivenessCheck(StackSym* typeSym); + Value * FindObjectTypeValueNoLivenessCheck(SymID typeSymId); Value * FindFuturePropertyValue(PropertySym *const propertySym); StackSym * GetCopyPropSym(Sym * sym, Value * val); diff --git a/lib/Backend/GlobOptFields.cpp b/lib/Backend/GlobOptFields.cpp index a3bdf8e53a5..8d62a8f19fa 100644 --- a/lib/Backend/GlobOptFields.cpp +++ b/lib/Backend/GlobOptFields.cpp @@ -2309,6 +2309,79 @@ GlobOpt::IsSubsetOf(Js::EquivalentTypeSet * leftTypeSet, Js::EquivalentTypeSet * return Js::EquivalentTypeSet::IsSubsetOf(leftTypeSet, rightTypeSet); } +bool +GlobOpt::CompareCurrentTypesWithExpectedTypes(JsTypeValueInfo *valueInfo, IR::PropertySymOpnd * propertySymOpnd) +{ + bool isTypeDead = propertySymOpnd->IsTypeDead(); + + if (valueInfo == nullptr || (valueInfo->GetJsType() == nullptr && valueInfo->GetJsTypeSet() == nullptr)) + { + // No upstream types. Do a type check. + return !isTypeDead; + } + + if (!propertySymOpnd->HasEquivalentTypeSet() || propertySymOpnd->NeedsMonoCheck()) + { + JITTypeHolder opndType = propertySymOpnd->GetType(); + + if (valueInfo->GetJsType() != nullptr) + { + if (valueInfo->GetJsType() == propertySymOpnd->GetType()) + { + return true; + } + if (propertySymOpnd->HasInitialType() && valueInfo->GetJsType() == propertySymOpnd->GetInitialType()) + { + return !isTypeDead; + } + return false; + } + else + { + Assert(valueInfo->GetJsTypeSet()); + Js::EquivalentTypeSet *valueTypeSet = valueInfo->GetJsTypeSet(); + + if (valueTypeSet->Contains(opndType)) + { + return !isTypeDead; + } + if (propertySymOpnd->HasInitialType() && valueTypeSet->Contains(propertySymOpnd->GetInitialType())) + { + return !isTypeDead; + } + return false; + } + } + else + { + Js::EquivalentTypeSet * opndTypeSet = propertySymOpnd->GetEquivalentTypeSet(); + + if (valueInfo->GetJsType() != nullptr) + { + uint16 checkedTypeSetIndex; + if (opndTypeSet->Contains(valueInfo->GetJsType(), &checkedTypeSetIndex)) + { + return true; + } + return false; + } + else + { + if (IsSubsetOf(valueInfo->GetJsTypeSet(), opndTypeSet)) + { + return true; + } + if (propertySymOpnd->IsMono() ? + valueInfo->GetJsTypeSet()->Contains(propertySymOpnd->GetFirstEquivalentType()) : + IsSubsetOf(opndTypeSet, valueInfo->GetJsTypeSet())) + { + return true; + } + return false; + } + } +} + bool GlobOpt::ProcessPropOpInTypeCheckSeq(IR::Instr* instr, IR::PropertySymOpnd *opnd) { @@ -3061,6 +3134,21 @@ GlobOpt::CopyPropPropertySymObj(IR::SymOpnd *symOpnd, IR::Instr *instr) { IR::PropertySymOpnd *propertySymOpnd = symOpnd->AsPropertySymOpnd(); + if (propertySymOpnd->IsTypeCheckSeqCandidate()) + { + // If the new pointer sym's expected type(s) don't match those in the inline-cache-based data for this access, + // we probably have a mismatch and can't safely objtypespec. If the saved objtypespecfldinfo isn't right for + // the new type, then we'll do an incorrect property access. + StackSym * newTypeSym = copySym->GetObjectTypeSym(); + Value * newValue = currentBlock->globOptData.FindObjectTypeValueNoLivenessCheck(newTypeSym); + JsTypeValueInfo * newValueInfo = newValue ? newValue->GetValueInfo()->AsJsType() : nullptr; + bool shouldOptimize = CompareCurrentTypesWithExpectedTypes(newValueInfo, propertySymOpnd); + if (!shouldOptimize) + { + propertySymOpnd->SetTypeCheckSeqCandidate(false); + } + } + // This is no longer strictly necessary, since we don't set the type dead bits in the initial // backward pass, but let's keep it around for now in case we choose to revert to the old model. propertySymOpnd->SetTypeDeadIfTypeCheckSeqCandidate(false); diff --git a/lib/Backend/Inline.cpp b/lib/Backend/Inline.cpp index e56c34b61c5..4977bb317a2 100644 --- a/lib/Backend/Inline.cpp +++ b/lib/Backend/Inline.cpp @@ -2706,6 +2706,13 @@ bool Inline::InlineApplyScriptTarget(IR::Instr *callInstr, const FunctionJITTime return false; }); + // If the arguments object was passed in as the first argument to apply, + // 'arguments' access continues to exist even after apply target inlining + if (!HasArgumentsAccess(explicitThisArgOut)) + { + callInstr->m_func->SetApplyTargetInliningRemovedArgumentsAccess(); + } + if (safeThis) { IR::Instr * byteCodeArgOutCapture = explicitThisArgOut->GetBytecodeArgOutCapture(); @@ -3139,11 +3146,6 @@ Inline::TryGetFixedMethodsForBuiltInAndTarget(IR::Instr *callInstr, const Functi return false; } - if (isApplyTarget) - { - callInstr->m_func->SetHasApplyTargetInlining(); - } - Assert(callInstr->m_opcode == originalCallOpCode); callInstr->ReplaceSrc1(builtInLdInstr->GetDst()); @@ -5293,21 +5295,39 @@ Inline::IsArgumentsOpnd(IR::Opnd* opnd, SymID argumentsSymId) return false; } +bool +Inline::IsArgumentsOpnd(IR::Opnd* opnd) +{ + IR::Opnd * checkOpnd = opnd; + while (checkOpnd) + { + if (checkOpnd->IsArgumentsObject()) + { + return true; + } + checkOpnd = checkOpnd->GetStackSym() && checkOpnd->GetStackSym()->IsSingleDef() ? checkOpnd->GetStackSym()->GetInstrDef()->GetSrc1() : nullptr; + } + + return false; +} bool Inline::HasArgumentsAccess(IR::Opnd *opnd, SymID argumentsSymId) { // We should look at dst last to correctly handle cases where it's the same as one of the src operands. - if (opnd) + IR::Opnd * checkOpnd = opnd; + while (checkOpnd) { - if (opnd->IsRegOpnd() || opnd->IsSymOpnd() || opnd->IsIndirOpnd()) + if (checkOpnd->IsRegOpnd() || checkOpnd->IsSymOpnd() || checkOpnd->IsIndirOpnd()) { - if (IsArgumentsOpnd(opnd, argumentsSymId)) + if (IsArgumentsOpnd(checkOpnd, argumentsSymId)) { return true; } } + checkOpnd = checkOpnd->GetStackSym() && checkOpnd->GetStackSym()->IsSingleDef() ? checkOpnd->GetStackSym()->GetInstrDef()->GetSrc1() : nullptr; } + return false; } @@ -5340,6 +5360,13 @@ Inline::HasArgumentsAccess(IR::Instr * instr, SymID argumentsSymId) return false; } +bool +Inline::HasArgumentsAccess(IR::Instr * instr) +{ + return (instr->GetSrc1() && IsArgumentsOpnd(instr->GetSrc1())) || + (instr->GetSrc2() && IsArgumentsOpnd(instr->GetSrc2())); +} + bool Inline::GetInlineeHasArgumentObject(Func * inlinee) { @@ -5351,14 +5378,12 @@ Inline::GetInlineeHasArgumentObject(Func * inlinee) // Inlinee has arguments access - if (!inlinee->GetHasApplyTargetInlining()) + if (!inlinee->GetApplyTargetInliningRemovedArgumentsAccess()) { - // There is no apply target inlining (this.init.apply(this, arguments)) - // So arguments access continues to exist return true; } - // Its possible there is no more arguments access after we inline apply target validate the same. + // Its possible there is no more arguments access after we inline apply target; validate the same. // This sounds expensive, but we are only walking inlinee which has apply target inlining optimization enabled. // Also we walk only instruction in that inlinee and not nested inlinees. So it is not expensive. SymID argumentsSymId = 0; diff --git a/lib/Backend/Inline.h b/lib/Backend/Inline.h index ee615269371..63e2cef89c6 100644 --- a/lib/Backend/Inline.h +++ b/lib/Backend/Inline.h @@ -91,8 +91,10 @@ class Inline IR::Instr * RemoveLdThis(IR::Instr *instr); bool GetInlineeHasArgumentObject(Func * inlinee); bool HasArgumentsAccess(IR::Instr * instr, SymID argumentsSymId); + bool HasArgumentsAccess(IR::Instr * instr); bool HasArgumentsAccess(IR::Opnd * opnd, SymID argumentsSymId); bool IsArgumentsOpnd(IR::Opnd* opnd,SymID argumentsSymId); + bool IsArgumentsOpnd(IR::Opnd* opnd); void Cleanup(IR::Instr *callInstr); IR::PropertySymOpnd* GetMethodLdOpndForCallInstr(IR::Instr* callInstr); #ifdef ENABLE_SIMDJS diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index 7e3eafe9a03..2cf345e7b80 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -17023,20 +17023,17 @@ Lowerer::GenerateFastStElemI(IR::Instr *& stElem, bool *instrIsInHelperBlockRef) const IR::AutoReuseOpnd autoReuseReg(reg, m_func); InsertMove(reg, src, stElem); - bool bailOutOnHelperCall = stElem->HasBailOutInfo() && (stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall); - // Convert to float, and assign to indirOpnd if (baseValueType.IsLikelyOptimizedVirtualTypedArray()) { IR::RegOpnd* dstReg = IR::RegOpnd::New(indirOpnd->GetType(), this->m_func); - m_lowererMD.EmitLoadFloat(dstReg, reg, stElem, bailOutOnHelperCall); + m_lowererMD.EmitLoadFloat(dstReg, reg, stElem, stElem, labelHelper); InsertMove(indirOpnd, dstReg, stElem); } else { - m_lowererMD.EmitLoadFloat(indirOpnd, reg, stElem, bailOutOnHelperCall); + m_lowererMD.EmitLoadFloat(indirOpnd, reg, stElem, stElem, labelHelper); } - } } else if (objectType == ObjectType::Uint8ClampedArray || objectType == ObjectType::Uint8ClampedVirtualArray || objectType == ObjectType::Uint8ClampedMixedArray) @@ -17219,7 +17216,7 @@ Lowerer::GenerateFastStElemI(IR::Instr *& stElem, bool *instrIsInHelperBlockRef) // Any pointer is larger than 512 because first 64k memory is reserved by the OS // #endif - IR::LabelInstr *labelInlineSet = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); + IR::LabelInstr *labelInlineSet = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); #ifndef _M_ARM // TEST src, ~(TaggedInt(255)) -- Check for tagged int >= 255 and <= 0 // JEQ $inlineSet @@ -17241,29 +17238,53 @@ Lowerer::GenerateFastStElemI(IR::Instr *& stElem, bool *instrIsInHelperBlockRef) // Uint8ClampedArray::DirectSetItem(array, index, value); - m_lowererMD.LoadHelperArgument(stElem, regSrc); - IR::Opnd *indexOpnd = indirOpnd->GetIndexOpnd(); - if (indexOpnd == nullptr) + // Inserting a helper call. Make sure it observes the main instructions's requirements regarding implicit calls. + if (!instrIsInHelperBlock) { - indexOpnd = IR::IntConstOpnd::New(indirOpnd->GetOffset(), TyInt32, this->m_func); + stElem->InsertBefore(IR::LabelInstr::New(Js::OpCode::Label, m_func, true)); } - else + + if (stElem->HasBailOutInfo() && (stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall)) { - Assert(indirOpnd->GetOffset() == 0); + // Bail out instead of doing the helper call. + Assert(labelHelper); + this->InsertBranch(Js::OpCode::Br, labelHelper, stElem); } - m_lowererMD.LoadHelperArgument(stElem, indexOpnd); - m_lowererMD.LoadHelperArgument(stElem, stElem->GetDst()->AsIndirOpnd()->GetBaseOpnd()); + else + { + IR::Instr *instr = IR::Instr::New(Js::OpCode::Call, this->m_func); + stElem->InsertBefore(instr); - IR::Instr *instr = IR::Instr::New(Js::OpCode::Call, this->m_func); + if (stElem->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(stElem->GetBailOutKind())) + { + // Bail out if this helper triggers implicit calls. + instr = instr->ConvertToBailOutInstr(stElem->GetBailOutInfo(), stElem->GetBailOutKind()); + if (stElem->GetBailOutInfo()->bailOutInstr == stElem) + { + IR::Instr * instrShare = stElem->ShareBailOut(); + LowerBailTarget(instrShare); + } + } - Assert(objectType == ObjectType::Uint8ClampedArray || objectType == ObjectType::Uint8ClampedMixedArray || objectType == ObjectType::Uint8ClampedVirtualArray); - instr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperUint8ClampedArraySetItem, this->m_func)); + m_lowererMD.LoadHelperArgument(instr, regSrc); + IR::Opnd *indexOpnd = indirOpnd->GetIndexOpnd(); + if (indexOpnd == nullptr) + { + indexOpnd = IR::IntConstOpnd::New(indirOpnd->GetOffset(), TyInt32, this->m_func); + } + else + { + Assert(indirOpnd->GetOffset() == 0); + } + m_lowererMD.LoadHelperArgument(instr, indexOpnd); + m_lowererMD.LoadHelperArgument(instr, stElem->GetDst()->AsIndirOpnd()->GetBaseOpnd()); - stElem->InsertBefore(instr); - m_lowererMD.LowerCall(instr, 0); + Assert(objectType == ObjectType::Uint8ClampedArray || objectType == ObjectType::Uint8ClampedMixedArray || objectType == ObjectType::Uint8ClampedVirtualArray); + m_lowererMD.ChangeToHelperCall(instr, IR::JnHelperMethod::HelperUint8ClampedArraySetItem); - // JMP $fallThrough - InsertBranch(Js::OpCode::Br, labelFallThru, stElem); + // JMP $fallThrough + InsertBranch(Js::OpCode::Br, labelFallThru, stElem); + } //$inlineSet stElem->InsertBefore(labelInlineSet); @@ -17306,9 +17327,27 @@ Lowerer::GenerateFastStElemI(IR::Instr *& stElem, bool *instrIsInHelperBlockRef) AssertMsg(AutoSystemInfo::Data.SSE2Available(), "GloOpt shouldn't have specialized Uint32Array StElemI to float64 if SSE2 is unavailable."); #endif + bool bailOutOnHelperCall = stElem->HasBailOutInfo() ? !!(stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall) : false; + if (bailOutOnHelperCall) + { + if(!GlobOpt::DoEliminateArrayAccessHelperCall(this->m_func)) + { + // Array access helper call removal is already off for some reason. Prevent trying to rejit again + // because it won't help and the same thing will happen again. Just abort jitting this function. + if(PHASE_TRACE(Js::BailOutPhase, this->m_func)) + { + Output::Print(_u(" Aborting JIT because EliminateArrayAccessHelperCall is already off\n")); + Output::Flush(); + } + throw Js::OperationAbortedException(); + } + + throw Js::RejitException(RejitReason::ArrayAccessHelperCallEliminationDisabled); + } + IR::RegOpnd *const reg = IR::RegOpnd::New(TyInt32, this->m_func); const IR::AutoReuseOpnd autoReuseReg(reg, m_func); - m_lowererMD.EmitFloatToInt(reg, src, stElem); + m_lowererMD.EmitFloatToInt(reg, src, stElem, stElem, labelHelper); // MOV indirOpnd, reg InsertMove(indirOpnd, reg, stElem); @@ -17338,12 +17377,23 @@ Lowerer::GenerateFastStElemI(IR::Instr *& stElem, bool *instrIsInHelperBlockRef) // FromVar reg, Src IR::RegOpnd *const reg = IR::RegOpnd::New(TyInt32, this->m_func); const IR::AutoReuseOpnd autoReuseReg(reg, m_func); - IR::Instr *const instr = IR::Instr::New(Js::OpCode::FromVar, reg, regSrc, stElem->m_func); + IR::Instr * instr = IR::Instr::New(Js::OpCode::FromVar, reg, regSrc, stElem->m_func); stElem->InsertBefore(instr); // Convert reg to int32 // Note: ToUint32 is implemented as (uint32)ToInt32() - bool bailOutOnHelperCall = (stElem->HasBailOutInfo() && (stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall)); + IR::BailOutKind bailOutKind = stElem->HasBailOutInfo() ? stElem->GetBailOutKind() : IR::BailOutInvalid; + if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) + { + instr = instr->ConvertToBailOutInstr(stElem->GetBailOutInfo(), bailOutKind); + if (stElem->GetBailOutInfo()->bailOutInstr == stElem) + { + IR::Instr * instrShare = stElem->ShareBailOut(); + LowerBailTarget(instrShare); + } + } + + bool bailOutOnHelperCall = !!(bailOutKind & IR::BailOutOnArrayAccessHelperCall); m_lowererMD.EmitLoadInt32(instr, true /*conversionFromObjectAllowed*/, bailOutOnHelperCall, labelHelper); // MOV indirOpnd, reg diff --git a/lib/Backend/LowerMDShared.cpp b/lib/Backend/LowerMDShared.cpp index 75eb4181e91..e405df74029 100644 --- a/lib/Backend/LowerMDShared.cpp +++ b/lib/Backend/LowerMDShared.cpp @@ -6324,8 +6324,8 @@ LowererMD::EmitLoadFloatCommon(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertIn return labelDone; } -IR::RegOpnd * -LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, bool bailOutOnHelperCall) +void +LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, IR::Instr * instrBailOut, IR::LabelInstr * labelBailOut) { IR::LabelInstr *labelDone; IR::Instr *instr; @@ -6334,24 +6334,17 @@ LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, b if (labelDone == nullptr) { // We're done - return nullptr; + return; } - if (bailOutOnHelperCall) + IR::BailOutKind bailOutKind = instrBailOut && instrBailOut->HasBailOutInfo() ? instrBailOut->GetBailOutKind() : IR::BailOutInvalid; + if (bailOutKind & IR::BailOutOnArrayAccessHelperCall) { - if(!GlobOpt::DoEliminateArrayAccessHelperCall(this->m_func)) - { - // Array access helper call removal is already off for some reason. Prevent trying to rejit again - // because it won't help and the same thing will happen again. Just abort jitting this function. - if(PHASE_TRACE(Js::BailOutPhase, this->m_func)) - { - Output::Print(_u(" Aborting JIT because EliminateArrayAccessHelperCall is already off\n")); - Output::Flush(); - } - throw Js::OperationAbortedException(); - } - - throw Js::RejitException(RejitReason::ArrayAccessHelperCallEliminationDisabled); + // Bail out instead of making the helper call. + Assert(labelBailOut); + m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, insertInstr); + insertInstr->InsertBefore(labelDone); + return; } IR::Opnd *memAddress = dst; @@ -6377,6 +6370,18 @@ LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, b instr->SetSrc2(reg3Opnd); insertInstr->InsertBefore(instr); + if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) + { + _Analysis_assume_(instrBailOut != nullptr); + + instr = instr->ConvertToBailOutInstr(instrBailOut->GetBailOutInfo(), bailOutKind); + if (instrBailOut->GetBailOutInfo()->bailOutInstr == instrBailOut) + { + IR::Instr * instrShare = instrBailOut->ShareBailOut(); + m_lowerer->LowerBailTarget(instrShare); + } + } + IR::JnHelperMethod helper; if (dst->GetType() == TyFloat32) { @@ -6405,8 +6410,6 @@ LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, b } // $Done insertInstr->InsertBefore(labelDone); - - return nullptr; } void @@ -7947,13 +7950,26 @@ LowererMD::InsertConvertFloat64ToInt32(const RoundMode roundMode, IR::Opnd *cons } void -LowererMD::EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) +LowererMD::EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert, IR::Instr *instrBailOut, IR::LabelInstr * labelBailOut) { #ifdef _M_IX86 // We should only generate this if sse2 is available Assert(AutoSystemInfo::Data.SSE2Available()); #endif + IR::BailOutKind bailOutKind = IR::BailOutInvalid; + if (instrBailOut && instrBailOut->HasBailOutInfo()) + { + bailOutKind = instrBailOut->GetBailOutKind(); + if (bailOutKind & IR::BailOutOnArrayAccessHelperCall) + { + // Bail out instead of calling helper. If this is happening unconditionally, the caller should instead throw a rejit exception. + Assert(labelBailOut); + m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, instrInsert); + return; + } + } + IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); IR::Instr *instr; @@ -7970,11 +7986,25 @@ LowererMD::EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) EmitFloat32ToFloat64(arg, src, instrInsert); } - // dst = ToInt32Core(src); - LoadDoubleHelperArgument(instrInsert, arg); instr = IR::Instr::New(Js::OpCode::CALL, dst, this->m_func); instrInsert->InsertBefore(instr); + + if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) + { + _Analysis_assume_(instrBailOut != nullptr); + + instr = instr->ConvertToBailOutInstr(instrBailOut->GetBailOutInfo(), bailOutKind); + if (instrBailOut->GetBailOutInfo()->bailOutInstr == instrBailOut) + { + IR::Instr * instrShare = instrBailOut->ShareBailOut(); + m_lowerer->LowerBailTarget(instrShare); + } + } + + // dst = ToInt32Core(src); + LoadDoubleHelperArgument(instr, arg); + this->ChangeToHelperCall(instr, IR::HelperConv_ToInt32Core); // $Done diff --git a/lib/Backend/LowerMDShared.h b/lib/Backend/LowerMDShared.h index c8bae116e23..813ba30bfe4 100644 --- a/lib/Backend/LowerMDShared.h +++ b/lib/Backend/LowerMDShared.h @@ -211,14 +211,14 @@ class LowererMD void EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitUIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); - void EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); + void EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert, IR::Instr * instrBailOut = nullptr, IR::LabelInstr * labelBailOut = nullptr); void EmitInt64toFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitFloat32ToFloat64(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); static IR::Instr * InsertConvertFloat64ToInt32(const RoundMode roundMode, IR::Opnd *const dst, IR::Opnd *const src, IR::Instr *const insertBeforeInstr); void ConvertFloatToInt32(IR::Opnd* intOpnd, IR::Opnd* floatOpnd, IR::LabelInstr * labelHelper, IR::LabelInstr * labelDone, IR::Instr * instInsert); void EmitReinterpretPrimitive(IR::Opnd* dst, IR::Opnd* src, IR::Instr* insertBeforeInstr); void EmitLoadFloatFromNumber(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr); - IR::RegOpnd * EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, bool bailOutOnHelperCall = false); + void EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, IR::Instr * instrBailOut = nullptr, IR::LabelInstr * labelBailOut = nullptr); static void EmitNon32BitOvfCheck(IR::Instr *instr, IR::Instr *insertInstr, IR::LabelInstr* bailOutLabel); static void LowerInt4NegWithBailOut(IR::Instr *const instr, const IR::BailOutKind bailOutKind, IR::LabelInstr *const bailOutLabel, IR::LabelInstr *const skipBailOutLabel); diff --git a/lib/Backend/Opnd.cpp b/lib/Backend/Opnd.cpp index 0021a14b343..e9a93e22074 100644 --- a/lib/Backend/Opnd.cpp +++ b/lib/Backend/Opnd.cpp @@ -2806,8 +2806,7 @@ Opnd::IsArgumentsObject() // Since we need this information in the inliner where we don't track arguments object sym, going with single def is the best option. StackSym * sym = this->GetStackSym(); - return sym && sym->IsSingleDef() && - (sym->m_instrDef->m_opcode == Js::OpCode::LdHeapArguments || sym->m_instrDef->m_opcode == Js::OpCode::LdLetHeapArguments); + return sym && sym->IsSingleDef() && sym->GetInstrDef()->HasAnyLoadHeapArgsOpCode(); } #if DBG_DUMP || defined(ENABLE_IR_VIEWER) diff --git a/lib/Backend/arm/LowerMD.cpp b/lib/Backend/arm/LowerMD.cpp index 7cf916de14f..16b65810f0f 100644 --- a/lib/Backend/arm/LowerMD.cpp +++ b/lib/Backend/arm/LowerMD.cpp @@ -5764,8 +5764,8 @@ LowererMD::EmitLoadFloatCommon(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertIn return labelDone; } -IR::RegOpnd * -LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, bool bailOutOnHelperCall) +void +LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, IR::Instr * instrBailOut, IR::LabelInstr * labelBailOut) { IR::LabelInstr *labelDone; IR::Instr *instr; @@ -5784,24 +5784,18 @@ LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, b if (labelDone == nullptr) { // We're done - return nullptr; + return; } - if (bailOutOnHelperCall) - { - if(!GlobOpt::DoEliminateArrayAccessHelperCall(this->m_func)) - { - // Array access helper call removal is already off for some reason. Prevent trying to rejit again - // because it won't help and the same thing will happen again. Just abort jitting this function. - if(PHASE_TRACE(Js::BailOutPhase, this->m_func)) - { - Output::Print(_u(" Aborting JIT because EliminateArrayAccessHelperCall is already off\n")); - Output::Flush(); - } - throw Js::OperationAbortedException(); - } + IR::BailOutKind bailOutKind = instrBailOut && instrBailOut->HasBailOutInfo() ? instrBailOut->GetBailOutKind() : IR::BailOutInvalid; - throw Js::RejitException(RejitReason::ArrayAccessHelperCallEliminationDisabled); + if (bailOutKind & IR::BailOutOnArrayAccessHelperCall) + { + // Bail out instead of making the helper call. + Assert(labelBailOut); + m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, insertInstr); + insertInstr->InsertBefore(labelDone); + return; } IR::Opnd *memAddress = dst; @@ -5834,6 +5828,17 @@ LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, b instr->SetSrc2(reg3Opnd); insertInstr->InsertBefore(instr); + if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) + { + _Analysis_assume_(instrBailOut != nullptr); + instr = instr->ConvertToBailOutInstr(instrBailOut->GetBailOutInfo(), bailOutKind); + if (instrBailOut->GetBailOutInfo()->bailOutInstr == instrBailOut) + { + IR::Instr * instrShare = instrBailOut->ShareBailOut(); + m_lowerer->LowerBailTarget(instrShare); + } + } + IR::JnHelperMethod helper; if (dst->GetType() == TyFloat32) { @@ -5856,7 +5861,6 @@ LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, b // $Done insertInstr->InsertBefore(labelDone); - return nullptr; } void @@ -8421,8 +8425,21 @@ LowererMD::CheckOverflowOnFloatToInt32(IR::Instr* instrInsert, IR::Opnd* intOpnd } void -LowererMD::EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) +LowererMD::EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert, IR::Instr * instrBailOut, IR::LabelInstr * labelBailOut) { + IR::BailOutKind bailOutKind = IR::BailOutInvalid; + if (instrBailOut && instrBailOut->HasBailOutInfo()) + { + bailOutKind = instrBailOut->GetBailOutKind(); + if (bailOutKind & IR::BailOutOnArrayAccessHelperCall) + { + // Bail out instead of calling helper. If this is happening unconditionally, the caller should instead throw a rejit exception. + Assert(labelBailOut); + m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, instrInsert); + return; + } + } + IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); IR::Instr *instr; @@ -8432,11 +8449,23 @@ LowererMD::EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) // $Helper instrInsert->InsertBefore(labelHelper); - // dst = ToInt32Core(src); - LoadDoubleHelperArgument(instrInsert, src); - instr = IR::Instr::New(Js::OpCode::Call, dst, this->m_func); instrInsert->InsertBefore(instr); + + if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) + { + _Analysis_assume_(instrBailOut != nullptr); + instr = instr->ConvertToBailOutInstr(instrBailOut->GetBailOutInfo(), bailOutKind); + if (instrBailOut->GetBailOutInfo()->bailOutInstr == instrBailOut) + { + IR::Instr * instrShare = instrBailOut->ShareBailOut(); + m_lowerer->LowerBailTarget(instrShare); + } + } + + // dst = ToInt32Core(src); + LoadDoubleHelperArgument(instr, src); + this->ChangeToHelperCall(instr, IR::HelperConv_ToInt32Core); // $Done diff --git a/lib/Backend/arm/LowerMD.h b/lib/Backend/arm/LowerMD.h index a285297acc1..9a719a6c2b2 100644 --- a/lib/Backend/arm/LowerMD.h +++ b/lib/Backend/arm/LowerMD.h @@ -154,7 +154,7 @@ class LowererMD void GenerateNumberAllocation(IR::RegOpnd * opndDst, IR::Instr * instrInsert, bool isHelper); void GenerateFastRecyclerAlloc(size_t allocSize, IR::RegOpnd* newObjDst, IR::Instr* insertionPointInstr, IR::LabelInstr* allocHelperLabel, IR::LabelInstr* allocDoneLabel); void SaveDoubleToVar(IR::RegOpnd * dstOpnd, IR::RegOpnd *opndFloat, IR::Instr *instrOrig, IR::Instr *instrInsert, bool isHelper = false); - IR::RegOpnd * EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, bool bailOutOnHelperCall = false); + void EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, IR::Instr * instrBailOut = nullptr, IR::LabelInstr * labelBailOut = nullptr); IR::Instr * LoadCheckedFloat(IR::RegOpnd *opndOrig, IR::RegOpnd *opndFloat, IR::LabelInstr *labelInline, IR::LabelInstr *labelHelper, IR::Instr *instrInsert, const bool checkForNullInLoopBody = false); void LoadFloatValue(IR::RegOpnd * javascriptNumber, IR::RegOpnd * opndFloat, IR::LabelInstr * labelHelper, IR::Instr * instrInsert, const bool checkFornullptrInLoopBody = false); @@ -201,7 +201,7 @@ class LowererMD void EmitLoadVarNoCheck(IR::RegOpnd * dst, IR::RegOpnd * src, IR::Instr *instrLoad, bool isFromUint32, bool isHelper); void EmitIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); void EmitUIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); - void EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert); + void EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert, IR::Instr * instrBailOut = nullptr, IR::LabelInstr * labelBailOut = nullptr); void EmitFloat32ToFloat64(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { Assert(UNREACHED); } void EmitInt64toFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { Assert(UNREACHED); } static IR::Instr * InsertConvertFloat64ToInt32(const RoundMode roundMode, IR::Opnd *const dst, IR::Opnd *const src, IR::Instr *const insertBeforeInstr); diff --git a/lib/Backend/arm64/LowerMD.h b/lib/Backend/arm64/LowerMD.h index 113b8fb58cd..efb32b419e8 100644 --- a/lib/Backend/arm64/LowerMD.h +++ b/lib/Backend/arm64/LowerMD.h @@ -150,7 +150,7 @@ class LowererMD void GenerateNumberAllocation(IR::RegOpnd * opndDst, IR::Instr * instrInsert, bool isHelper) { __debugbreak(); } void GenerateFastRecyclerAlloc(size_t allocSize, IR::RegOpnd* newObjDst, IR::Instr* insertionPointInstr, IR::LabelInstr* allocHelperLabel, IR::LabelInstr* allocDoneLabel) { __debugbreak(); } void SaveDoubleToVar(IR::RegOpnd * dstOpnd, IR::RegOpnd *opndFloat, IR::Instr *instrOrig, IR::Instr *instrInsert, bool isHelper = false) { __debugbreak(); } - IR::RegOpnd * EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, bool bailOutOnHelperCall = false) { __debugbreak(); return 0; } + void EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, IR::Instr * instrBailOut = nullptr, IR::LabelInstr * labelBailOut = nullptr) { __debugbreak(); } IR::Instr * LoadCheckedFloat(IR::RegOpnd *opndOrig, IR::RegOpnd *opndFloat, IR::LabelInstr *labelInline, IR::LabelInstr *labelHelper, IR::Instr *instrInsert) { __debugbreak(); return 0; } void LoadFloatValue(IR::RegOpnd * javascriptNumber, IR::RegOpnd * opndFloat, IR::LabelInstr * labelHelper, IR::Instr * instrInsert) { __debugbreak(); } @@ -197,7 +197,7 @@ class LowererMD void EmitLoadVarNoCheck(IR::RegOpnd * dst, IR::RegOpnd * src, IR::Instr *instrLoad, bool isFromUint32, bool isHelper) { __debugbreak(); } void EmitIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); } void EmitUIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); } - void EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); } + void EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert, IR::Instr * instrBailOut = nullptr, IR::LabelInstr * labelBailOut = nullptr) { __debugbreak(); } void EmitFloat32ToFloat64(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); } void EmitInt64toFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); } void EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); } diff --git a/lib/Common/Common/Int32Math.h b/lib/Common/Common/Int32Math.h index b83f10a821c..4154cc55b18 100644 --- a/lib/Common/Common/Int32Math.h +++ b/lib/Common/Common/Int32Math.h @@ -16,3 +16,15 @@ class Int32Math: public IntMathCommon static bool Shr(int32 left, int32 right, int32 *pResult); static bool ShrU(int32 left, int32 right, int32 *pResult); }; + +template <> +inline bool Math::IncImpl(int32 val, int32 *pResult) +{ + return Int32Math::Inc(val, pResult); +} + +template <> +inline bool Math::AddImpl(int32 left, int32 right, int32 *pResult) +{ + return Int32Math::Add(left, right, pResult); +} diff --git a/lib/Common/Common/MathUtil.h b/lib/Common/Common/MathUtil.h index aceb25d158b..a5080bcdbe6 100644 --- a/lib/Common/Common/MathUtil.h +++ b/lib/Common/Common/MathUtil.h @@ -124,4 +124,54 @@ class Math return AlignOverflowCheck(size, alignment, DefaultOverflowPolicy); } + // Postfix increment "val++", call overflowFn() first if overflow + template + static T PostInc(T& val, const Func& overflowFn) + { + T tmp = val; + if (IncImpl(val, &tmp)) + { + overflowFn(); // call before changing val + } + + T old = val; + val = tmp; + return old; + } + + // Postfix increment "val++", call DefaultOverflowPolicy() first if overflow + template + static T PostInc(T& val) + { + return PostInc(val, DefaultOverflowPolicy); + } + + template + static bool IncImpl(T val, T *pResult) + { + CompileAssert(false); // must implement template specialization on type T + } + + template + static T Add(T left, T right, const Func& overflowFn) + { + T result; + if (AddImpl(left, right, &result)) + { + overflowFn(); + } + return result; + } + + template + static T Add(T left, T right) + { + return Add(left, right, DefaultOverflowPolicy); + } + + template + static bool AddImpl(T left, T right, T *pResult) + { + CompileAssert(false); // must implement template specialization on type T + } }; diff --git a/lib/Common/Common/UInt16Math.h b/lib/Common/Common/UInt16Math.h index 330e39f32d8..4ce1c77ba19 100644 --- a/lib/Common/Common/UInt16Math.h +++ b/lib/Common/Common/UInt16Math.h @@ -2,6 +2,8 @@ // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- +#pragma once + class UInt16Math { public: @@ -80,3 +82,15 @@ class UInt16Math }; using ArgSlotMath = UInt16Math; + +template <> +inline bool Math::IncImpl(uint16 val, uint16 *pResult) +{ + return UInt16Math::Add(val, 1, pResult); +} + +template <> +inline bool Math::AddImpl(uint16 left, uint16 right, uint16 *pResult) +{ + return UInt16Math::Add(left, right, pResult); +} diff --git a/lib/Runtime/Base/ScriptContext.h b/lib/Runtime/Base/ScriptContext.h index 0bcc044b974..b7440769757 100644 --- a/lib/Runtime/Base/ScriptContext.h +++ b/lib/Runtime/Base/ScriptContext.h @@ -437,8 +437,9 @@ namespace Js bool IsRegistered() { return next != nullptr || prev != nullptr || threadContext->GetScriptContextList() == this; } union { - int64 int64Val; // stores the double & float result for Asm interpreter - double dbVal; // stores the double & float result for Asm interpreter + int64 int64Val; + float floatVal; + double dbVal; AsmJsSIMDValue simdVal; // stores raw simd result for Asm interpreter } asmJsReturnValue; static DWORD GetAsmJsReturnValueOffset() { return offsetof(ScriptContext, asmJsReturnValue); } diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index 24bd409f6a1..628f11676ec 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -16,6 +16,7 @@ void EmitYield(Js::RegSlot inputLocation, Js::RegSlot resultLocation, ByteCodeGe void EmitUseBeforeDeclaration(Symbol *sym, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo); void EmitUseBeforeDeclarationRuntimeError(ByteCodeGenerator *byteCodeGenerator, Js::RegSlot location); void VisitClearTmpRegs(ParseNode * pnode, ByteCodeGenerator * byteCodeGenerator, FuncInfo * funcInfo); +void EmitSuperMethodBegin(ParseNode *pnodeTarget, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo); bool CallTargetIsArray(ParseNode *pnode) { @@ -2632,6 +2633,10 @@ void ByteCodeGenerator::EmitThis(FuncInfo *funcInfo, Js::RegSlot fromRegister) Js::PropertyId slot = parent->thisScopeSlot; EmitInternalScopedSlotLoad(funcInfo, scope, envIndex, slot, funcInfo->thisPointerRegister, false); } + else + { + m_writer.Reg1(Js::OpCode::LdUndef, funcInfo->thisPointerRegister); + } } else if (funcInfo->byteCodeFunction->GetIsStrictMode() && (!funcInfo->IsGlobalFunction() || this->flags & fscrEval)) { @@ -7084,6 +7089,7 @@ void EmitAssignment( Js::PropertyId propertyId = lhs->sxBin.pnode2->sxPid.PropertyIdFromNameNode(); uint cacheId = funcInfo->FindOrAddInlineCacheId(lhs->sxBin.pnode1->location, propertyId, false, true); + EmitSuperMethodBegin(lhs, byteCodeGenerator, funcInfo); if (lhs->sxBin.pnode1->nop == knopSuper) { Js::RegSlot tmpReg = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, funcInfo->superRegister, funcInfo); diff --git a/lib/Runtime/Language/AsmJsUtils.cpp b/lib/Runtime/Language/AsmJsUtils.cpp index 7c4fb85bc20..b2c31719f96 100644 --- a/lib/Runtime/Language/AsmJsUtils.cpp +++ b/lib/Runtime/Language/AsmJsUtils.cpp @@ -427,18 +427,7 @@ namespace Js int GetStackSizeForAsmJsUnboxing(ScriptFunction* func) { AsmJsFunctionInfo* info = func->GetFunctionBody()->GetAsmJsFunctionInfo(); - int argSize = MachPtr; - for (ArgSlot i = 0; i < info->GetArgCount(); i++) - { - if (info->GetArgType(i).isSIMD()) - { - argSize += sizeof(AsmJsSIMDValue); - } - else - { - argSize += MachPtr; - } - } + int argSize = info->GetArgByteSize() + MachPtr; argSize = ::Math::Align(argSize, 16); if (argSize < 32) @@ -446,7 +435,7 @@ namespace Js argSize = 32; // convention is to always allocate spill space for rcx,rdx,r8,r9 } - PROBE_STACK_CALL(func->GetScriptContext(), func, argSize); + PROBE_STACK_CALL(func->GetScriptContext(), func, argSize + Js::Constants::MinStackDefault); return argSize; } @@ -582,7 +571,7 @@ namespace Js FunctionBody* body = func->GetFunctionBody(); AsmJsFunctionInfo* info = body->GetAsmJsFunctionInfo(); int argSize = info->GetArgByteSize(); - char* dst; + void* dst; Var returnValue = 0; // TODO (michhol): wasm, heap should not ever be detached @@ -590,14 +579,10 @@ namespace Js argSize = ::Math::Align(argSize, 8); // Allocate stack space for args + PROBE_STACK_CALL(func->GetScriptContext(), func, argSize + Js::Constants::MinStackDefault); - __asm - { - sub esp, argSize - mov dst, esp - }; - - const void * asmJSEntryPoint = UnboxAsmJsArguments(func, args.Values + 1, dst - MachPtr, callInfo); + dst = _alloca(argSize); + const void * asmJSEntryPoint = UnboxAsmJsArguments(func, args.Values + 1, ((char*)dst) - MachPtr, callInfo); // make call and convert primitive type back to Var switch (info->GetReturnType().which()) diff --git a/lib/Runtime/Language/InterpreterStackFrame.cpp b/lib/Runtime/Language/InterpreterStackFrame.cpp index 0e4e0433c01..2588a937123 100644 --- a/lib/Runtime/Language/InterpreterStackFrame.cpp +++ b/lib/Runtime/Language/InterpreterStackFrame.cpp @@ -1633,7 +1633,7 @@ namespace Js jmp doSimd; // Otherwise, the return type is simd ToXmmWord: // float - cvtsd2ss xmm0, [eax]; + movss xmm0, [eax]; jmp end; ToXmmDWord: // double @@ -1802,8 +1802,9 @@ namespace Js #pragma optimize("", on) #endif - Var InterpreterStackFrame::InterpreterHelper(ScriptFunction* function, ArgumentReader args, void* returnAddress, void* addressOfReturnAddress, const bool isAsmJs) + Var InterpreterStackFrame::InterpreterHelper(ScriptFunction* function, ArgumentReader args, void* returnAddress, void* addressOfReturnAddress, AsmJsReturnStruct* asmJsReturn) { + const bool isAsmJs = asmJsReturn != nullptr; #ifdef ENABLE_DEBUG_CONFIG_OPTIONS // Support for simulating partially initialized interpreter stack frame. @@ -2068,6 +2069,17 @@ namespace Js } #endif +#ifdef ASMJS_PLAT + if (isAsmJs) + { + asmJsReturn->i = newInstance->GetRegRawInt(0); + asmJsReturn->l = newInstance->GetRegRawInt64(0); + asmJsReturn->d = newInstance->GetRegRawDouble(0); + asmJsReturn->f = newInstance->GetRegRawFloat(0); + asmJsReturn->simd = newInstance->GetRegRawSimd(0); + } +#endif + if (fReleaseAlloc) { functionScriptContext->ReleaseInterpreterArena(); @@ -2080,46 +2092,10 @@ namespace Js } #endif - if (isAsmJs) - { - return newInstance; - } return aReturn; } #ifdef ASMJS_PLAT - template<> - int InterpreterStackFrame::GetAsmJsRetVal(InterpreterStackFrame* instance) - { - return instance->m_localIntSlots[0]; - } - template<> - int64 InterpreterStackFrame::GetAsmJsRetVal(InterpreterStackFrame* instance) - { - return instance->m_localInt64Slots[0]; - } - template<> - double InterpreterStackFrame::GetAsmJsRetVal(InterpreterStackFrame* instance) - { - return instance->m_localDoubleSlots[0]; - } - template<> - float InterpreterStackFrame::GetAsmJsRetVal(InterpreterStackFrame* instance) - { - return instance->m_localFloatSlots[0]; - } - template<> - AsmJsSIMDValue InterpreterStackFrame::GetAsmJsRetVal(InterpreterStackFrame* instance) - { - return instance->m_localSimdSlots[0]; - } -#if _M_IX86 || _M_X64 - template<> - X86SIMDValue InterpreterStackFrame::GetAsmJsRetVal(InterpreterStackFrame* instance) - { - return X86SIMDValue::ToX86SIMDValue(instance->m_localSimdSlots[0]); - } -#endif #if _M_IX86 int InterpreterStackFrame::AsmJsInterpreter(AsmJsCallStackLayout* stack) @@ -2135,7 +2111,8 @@ namespace Js #if ENABLE_PROFILE_INFO function->GetFunctionBody()->EnsureDynamicProfileInfo(); #endif - InterpreterStackFrame* newInstance = (InterpreterStackFrame*)InterpreterHelper(function, args, returnAddress, addressOfReturnAddress, true); + AsmJsReturnStruct asmJsReturn = { 0 }; + InterpreterHelper(function, args, returnAddress, addressOfReturnAddress, &asmJsReturn); //Handle return value AsmJsRetType::Which retType = (AsmJsRetType::Which) GetRetType(function); @@ -2157,31 +2134,30 @@ namespace Js #ifdef ENABLE_SIMDJS if (function->GetScriptContext()->GetConfig()->IsSimdjsEnabled()) { - function->GetScriptContext()->asmJsReturnValue.simdVal = GetAsmJsRetVal(newInstance); + function->GetScriptContext()->asmJsReturnValue.simdVal = asmJsReturn.simd; break; } #endif Assert(UNREACHED); // double return case AsmJsRetType::Double: - function->GetScriptContext()->asmJsReturnValue.dbVal = GetAsmJsRetVal(newInstance); + function->GetScriptContext()->asmJsReturnValue.dbVal = asmJsReturn.d; break; // float return case AsmJsRetType::Float: - function->GetScriptContext()->asmJsReturnValue.dbVal = (double)GetAsmJsRetVal(newInstance); + function->GetScriptContext()->asmJsReturnValue.floatVal = asmJsReturn.f; break; // signed or void return case AsmJsRetType::Signed: case AsmJsRetType::Void: - retVal = GetAsmJsRetVal(newInstance); + retVal = asmJsReturn.i; break; case AsmJsRetType::Int64: { - int64 int64RetVal = GetAsmJsRetVal(newInstance); - function->GetScriptContext()->asmJsReturnValue.int64Val = int64RetVal; + function->GetScriptContext()->asmJsReturnValue.int64Val = asmJsReturn.l; // put the lower bits into eax // we'll read the higher bits from memory - retVal = (int)int64RetVal; + retVal = (int)asmJsReturn.l; break; } default: @@ -2259,9 +2235,10 @@ namespace Js void* returnAddress = _ReturnAddress(); void* addressOfReturnAddress = _AddressOfReturnAddress(); function->GetFunctionBody()->EnsureDynamicProfileInfo(); - InterpreterStackFrame* newInstance = (InterpreterStackFrame*)InterpreterHelper(function, args, returnAddress, addressOfReturnAddress, true); + AsmJsReturnStruct asmJsReturn = { 0 }; + InterpreterHelper(function, args, returnAddress, addressOfReturnAddress, &asmJsReturn); - return GetAsmJsRetVal(newInstance); + return asmJsReturn.GetRetVal(); } __m128 InterpreterStackFrame::AsmJsInterpreterSimdJs(AsmJsCallStackLayout* layout) diff --git a/lib/Runtime/Language/InterpreterStackFrame.h b/lib/Runtime/Language/InterpreterStackFrame.h index a006c46890f..82549b75073 100644 --- a/lib/Runtime/Language/InterpreterStackFrame.h +++ b/lib/Runtime/Language/InterpreterStackFrame.h @@ -77,6 +77,25 @@ namespace Js bool bailedOut; bool bailedOutOfInlinee; }; + + struct AsmJsReturnStruct + { +#ifdef ASMJS_PLAT + int32 i; + int64 l; + float f; + double d; + AsmJsSIMDValue simd; + + template T GetRetVal(); + template<> int32 GetRetVal() { return i; } + template<> int64 GetRetVal() { return l; } + template<> float GetRetVal() { return f; } + template<> double GetRetVal() { return d; } + template<> X86SIMDValue GetRetVal() { return X86SIMDValue::ToX86SIMDValue(simd); } +#endif + }; + private: ByteCodeReader m_reader; // Reader for current function int m_inSlotsCount; // Count of actual incoming parameters to this function @@ -297,8 +316,6 @@ namespace Js DWORD_PTR GetStackAddress() const; void* GetAddressOfReturnAddress() const; - template - static T GetAsmJsRetVal(InterpreterStackFrame* instance); #if _M_IX86 static int GetRetType(JavascriptFunction* func); static int GetAsmJsArgSize(AsmJsCallStackLayout * stack); @@ -328,7 +345,7 @@ namespace Js #else _NOINLINE static Var InterpreterThunk(RecyclableObject* function, CallInfo callInfo, ...); #endif - static Var InterpreterHelper(ScriptFunction* function, ArgumentReader args, void* returnAddress, void* addressOfReturnAddress, const bool isAsmJs = false); + static Var InterpreterHelper(ScriptFunction* function, ArgumentReader args, void* returnAddress, void* addressOfReturnAddress, AsmJsReturnStruct* asmReturn = nullptr); private: #if DYNAMIC_INTERPRETER_THUNK static JavascriptMethod EnsureDynamicInterpreterThunk(Js::ScriptFunction * function); @@ -629,7 +646,6 @@ namespace Js template inline void OP_LdArrFunc(const unaligned T* playout); template inline void OP_LdArrWasmFunc(const unaligned T* playout); template inline void OP_CheckSignature(const unaligned T* playout); - template inline void OP_ReturnDb(const unaligned T* playout); template T GetArrayViewOverflowVal(); template inline void OP_StArr( uint32 index, RegSlot value ); template inline Var OP_LdAsmJsSlot(Var instance, const unaligned T* playout ); diff --git a/lib/Runtime/Language/amd64/amd64_Thunks.asm b/lib/Runtime/Language/amd64/amd64_Thunks.asm index fe91e98a501..f219bfcd8e0 100644 --- a/lib/Runtime/Language/amd64/amd64_Thunks.asm +++ b/lib/Runtime/Language/amd64/amd64_Thunks.asm @@ -10,6 +10,7 @@ ifdef _CONTROL_FLOW_GUARD extrn __guard_check_icall_fptr:QWORD extrn __guard_dispatch_icall_fptr:QWORD endif +extrn __chkstk: PROC ifdef _ENABLE_DYNAMIC_THUNKS @@ -346,7 +347,13 @@ align 16 mov rdi, rdx ; save orig stack pointer, so that we can add it back later add rdx, 68h ; account for the changes we have already made to rsp + ; Check if we need to commit more stack + cmp rax, 2000h ; x64 has 2 guard pages + jl stack_alloc + call __chkstk +stack_alloc: sub rsp, rax ; allocate additional stack space for args + ; UnboxAsmJsArguments(func, origArgsLoc, argDst, callInfo) mov rcx, rsi mov r8, rsp diff --git a/lib/Runtime/Library/JavascriptArray.cpp b/lib/Runtime/Library/JavascriptArray.cpp index cea6656a6c9..cef9eddc9aa 100644 --- a/lib/Runtime/Library/JavascriptArray.cpp +++ b/lib/Runtime/Library/JavascriptArray.cpp @@ -1603,6 +1603,9 @@ namespace Js // Grow the segments + // Code below has potential to throw due to OOM or SO. Just FailFast on those cases + AutoFailFastOnError failFastError; + ScriptContext *scriptContext = intArray->GetScriptContext(); Recycler *recycler = scriptContext->GetRecycler(); SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr; @@ -1711,6 +1714,7 @@ namespace Js VirtualTableInfo::SetVirtualTable(intArray); } + failFastError.Completed(); return (JavascriptNativeFloatArray*)intArray; } @@ -1878,6 +1882,10 @@ namespace Js ScriptContext *scriptContext = intArray->GetScriptContext(); Recycler *recycler = scriptContext->GetRecycler(); SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr; + + // Code below has potential to throw due to OOM or SO. Just FailFast on those cases + AutoFailFastOnError failFastError; + for (seg = intArray->head; seg; seg = nextSeg) { nextSeg = seg->next; @@ -1988,6 +1996,7 @@ namespace Js VirtualTableInfo::SetVirtualTable(intArray); } + failFastError.Completed(); return intArray; } JavascriptArray *JavascriptNativeIntArray::ToVarArray(JavascriptNativeIntArray *intArray) @@ -2063,6 +2072,10 @@ namespace Js ScriptContext *scriptContext = fArray->GetScriptContext(); Recycler *recycler = scriptContext->GetRecycler(); SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr; + + // Code below has potential to throw due to OOM or SO. Just FailFast on those cases + AutoFailFastOnError failFastError; + for (seg = fArray->head; seg; seg = nextSeg) { nextSeg = seg->next; @@ -2185,6 +2198,8 @@ namespace Js VirtualTableInfo::SetVirtualTable(fArray); } + failFastError.Completed(); + return fArray; } @@ -7277,6 +7292,8 @@ namespace Js // If return object is a JavascriptArray, we can use all the array splice helpers if (newArr && isBuiltinArrayCtor && len == pArr->length) { + // Code below has potential to throw due to OOM or SO. Just FailFast on those cases + AutoFailFastOnError failFastOnError; // Array has a single segment (need not start at 0) and splice start lies in the range // of that segment we optimize splice - Fast path. @@ -7370,6 +7387,8 @@ namespace Js newArr->length = deleteLen; } + failFastOnError.Completed(); + newArr->InvalidateLastUsedSegment(); #ifdef VALIDATE_ARRAY @@ -7690,8 +7709,6 @@ namespace Js Assert(pArr->length <= MaxArrayLength - unshiftElements); - SparseArraySegmentBase* renumberSeg = pArr->head->next; - bool isIntArray = false; bool isFloatArray = false; @@ -7722,6 +7739,8 @@ namespace Js } } + SparseArraySegmentBase* renumberSeg = pArr->head->next; + while (renumberSeg) { renumberSeg->left += unshiftElements; @@ -12580,6 +12599,14 @@ namespace Js return static_cast(RecyclableObject::FromVar(aValue)); } + AutoFailFastOnError::~AutoFailFastOnError() + { + if (!m_operationCompleted) + { + AssertOrFailFast(false); + } + } + template int Js::JavascriptArray::GetParamForIndexOf(unsigned int, Js::Arguments const&, void*&, unsigned int&, Js::ScriptContext*); template bool Js::JavascriptArray::ArrayElementEnumerator::MoveNext(); template void Js::JavascriptArray::SetArrayLiteralItem(unsigned int, void*); diff --git a/lib/Runtime/Library/JavascriptArray.h b/lib/Runtime/Library/JavascriptArray.h index d8b780fd059..b976cf66b17 100644 --- a/lib/Runtime/Library/JavascriptArray.h +++ b/lib/Runtime/Library/JavascriptArray.h @@ -1263,4 +1263,19 @@ namespace Js template <> inline uint32 JavascriptArray::ConvertToIndex(uint32 idxDest, ScriptContext* scriptContext) { return idxDest; } + // This is for protecting a region of code, where we can't recover and be consistent upon failures (mainly due to OOM and SO). + // FailFast on that. + class AutoFailFastOnError + { + public: + AutoFailFastOnError() : m_operationCompleted(false) { } + ~AutoFailFastOnError(); + + void Completed() { m_operationCompleted = true; } + + private: + bool m_operationCompleted; + }; + + } // namespace Js diff --git a/lib/Runtime/Library/amd64/JavascriptFunctionA.asm b/lib/Runtime/Library/amd64/JavascriptFunctionA.asm index 80f91939446..f13a2a99a75 100644 --- a/lib/Runtime/Library/amd64/JavascriptFunctionA.asm +++ b/lib/Runtime/Library/amd64/JavascriptFunctionA.asm @@ -330,17 +330,17 @@ endif push rcx sub rsp, 20h call ?GetStackSizeForAsmJsUnboxing@Js@@YAHPEAVScriptFunction@1@@Z - mov r13, rax add rsp, 20h pop rcx - pop rax - -setup_stack_and_reg_args: - - ; OP_CallAsmInternal checks stack space + pop r13 + ; OP_CallAsmInternal probes stack + ; Check if we need to commit more stack + cmp rax, 2000h ; x64 has 2 guard pages + jl stack_alloc + call __chkstk stack_alloc: - sub rsp, r13 + sub rsp, rax ;; copy all args to the new stack frame. lea r11, [rsi] @@ -350,8 +350,8 @@ copy_stack_args: mov qword ptr [r10], rdi add r11, 8 add r10, 8 - sub r13, 8 - cmp r13, 0 + sub rax, 8 + cmp rax, 0 jg copy_stack_args ; r12 points to arg size map @@ -399,7 +399,7 @@ SIMDArg3: movups xmm3, xmmword ptr [r11] setup_args_done: - call rax + call r13 done: lea rsp, [rbp] pop rbp diff --git a/lib/Runtime/Types/DictionaryPropertyDescriptor.h b/lib/Runtime/Types/DictionaryPropertyDescriptor.h index 58e39388607..1f3c3f1c916 100644 --- a/lib/Runtime/Types/DictionaryPropertyDescriptor.h +++ b/lib/Runtime/Types/DictionaryPropertyDescriptor.h @@ -156,17 +156,17 @@ namespace Js Assert(this->Data == NoSlots); if (addingLetConstGlobal) { - this->Data = nextPropertyIndex++; + this->Data = ::Math::PostInc(nextPropertyIndex); } } else if (addingLetConstGlobal) { this->Getter = this->Data; - this->Data = nextPropertyIndex++; + this->Data = ::Math::PostInc(nextPropertyIndex); } else { - this->Getter = nextPropertyIndex++; + this->Getter = ::Math::PostInc(nextPropertyIndex); } this->Attributes |= PropertyLetConstGlobal; Assert((addingLetConstGlobal ? GetDataPropertyIndex() : GetDataPropertyIndex()) != NoSlots); @@ -222,12 +222,12 @@ namespace Js bool addedPropertyIndex = false; if (this->Getter == NoSlots) { - this->Getter = nextPropertyIndex++; + this->Getter = ::Math::PostInc(nextPropertyIndex); addedPropertyIndex = true; } if (this->Setter == NoSlots) { - this->Setter = nextPropertyIndex++; + this->Setter = ::Math::PostInc(nextPropertyIndex); addedPropertyIndex = true; } Assert(this->GetGetterPropertyIndex() != NoSlots || this->GetSetterPropertyIndex() != NoSlots); diff --git a/lib/Runtime/Types/DictionaryTypeHandler.cpp b/lib/Runtime/Types/DictionaryTypeHandler.cpp index 227ded452b7..ac9a5588918 100644 --- a/lib/Runtime/Types/DictionaryTypeHandler.cpp +++ b/lib/Runtime/Types/DictionaryTypeHandler.cpp @@ -16,7 +16,7 @@ namespace Js DictionaryTypeHandlerBase* DictionaryTypeHandlerBase::CreateTypeHandlerForArgumentsInStrictMode(Recycler * recycler, ScriptContext * scriptContext) { DictionaryTypeHandlerBase * dictTypeHandler = New(recycler, 8, 0, 0); - + dictTypeHandler->Add(scriptContext->GetPropertyName(Js::PropertyIds::caller), PropertyWritable, scriptContext); dictTypeHandler->Add(scriptContext->GetPropertyName(Js::PropertyIds::callee), PropertyWritable, scriptContext); dictTypeHandler->Add(scriptContext->GetPropertyName(Js::PropertyIds::length), PropertyBuiltInMethodDefaults, scriptContext); @@ -377,7 +377,7 @@ namespace Js { Assert(this->GetSlotCapacity() <= MaxPropertyIndexSize); // slotCapacity should never exceed MaxPropertyIndexSize Assert(nextPropertyIndex < this->GetSlotCapacity()); // nextPropertyIndex must be ready - T index = nextPropertyIndex++; + T index = ::Math::PostInc(nextPropertyIndex); DictionaryPropertyDescriptor descriptor(index, attributes); Assert((!isFixed && !usedAsFixed) || (!IsInternalPropertyId(propertyId->GetPropertyId()) && this->singletonInstance != nullptr)); @@ -1540,6 +1540,18 @@ namespace Js Assert(this->VerifyIsExtensible(scriptContext, false) || this->HasProperty(instance, propertyId) || JavascriptFunction::IsBuiltinProperty(instance, propertyId)); + // We could potentially need 2 new slots to hold getter/setter, try pre-reserve + if (this->GetSlotCapacity() - 2 < nextPropertyIndex) + { + if (this->GetSlotCapacity() > MaxPropertyIndexSize - 2) + { + return ConvertToBigDictionaryTypeHandler(instance) + ->SetAccessors(instance, propertyId, getter, setter, flags); + } + + this->EnsureSlotCapacity(instance, 2); + } + DictionaryPropertyDescriptor* descriptor; if (this->GetFlags() & IsPrototypeFlag) { @@ -1585,15 +1597,7 @@ namespace Js // conversion from data-property to accessor property if (descriptor->ConvertToGetterSetter(nextPropertyIndex)) { - if (this->GetSlotCapacity() <= nextPropertyIndex) - { - if (this->GetSlotCapacity() >= MaxPropertyIndexSize) - { - Throw::OutOfMemory(); - } - - this->EnsureSlotCapacity(instance); - } + AssertOrFailFast(this->GetSlotCapacity() >= nextPropertyIndex); // pre-reserved 2 at entry } // DictionaryTypeHandlers are not supposed to be shared. @@ -1673,18 +1677,10 @@ namespace Js getter = CanonicalizeAccessor(getter, library); setter = CanonicalizeAccessor(setter, library); - T getterIndex = nextPropertyIndex++; - T setterIndex = nextPropertyIndex++; + T getterIndex = ::Math::PostInc(nextPropertyIndex); + T setterIndex = ::Math::PostInc(nextPropertyIndex); DictionaryPropertyDescriptor newDescriptor(getterIndex, setterIndex); - if (this->GetSlotCapacity() <= nextPropertyIndex) - { - if (this->GetSlotCapacity() >= MaxPropertyIndexSize) - { - Throw::OutOfMemory(); - } - - this->EnsureSlotCapacity(instance); - } + AssertOrFailFast(this->GetSlotCapacity() >= nextPropertyIndex); // pre-reserved 2 at entry // DictionaryTypeHandlers are not supposed to be shared. Assert(!GetIsOrMayBecomeShared()); @@ -1785,6 +1781,18 @@ namespace Js } else if ((descriptor->Attributes & PropertyLetConstGlobal) != (attributes & PropertyLetConstGlobal)) { + // We could potentially need 1 new slot by AddShadowedData(), try pre-reserve + if (this->GetSlotCapacity() <= nextPropertyIndex) + { + if (this->GetSlotCapacity() >= MaxPropertyIndexSize) + { + return ConvertToBigDictionaryTypeHandler(instance)->SetPropertyWithAttributes( + instance, propertyId, value, attributes, info, flags, possibleSideEffects); + } + + this->EnsureSlotCapacity(instance); + } + bool addingLetConstGlobal = (attributes & PropertyLetConstGlobal) != 0; if (addingLetConstGlobal) @@ -1798,15 +1806,7 @@ namespace Js descriptor->AddShadowedData(nextPropertyIndex, addingLetConstGlobal); - if (this->GetSlotCapacity() <= nextPropertyIndex) - { - if (this->GetSlotCapacity() >= MaxPropertyIndexSize) - { - Throw::OutOfMemory(); - } - - this->EnsureSlotCapacity(instance); - } + AssertOrFailFast(this->GetSlotCapacity() >= nextPropertyIndex); // pre-reserved above if (addingLetConstGlobal) { @@ -1894,15 +1894,15 @@ namespace Js } template - void DictionaryTypeHandlerBase::EnsureSlotCapacity(DynamicObject * instance) + void DictionaryTypeHandlerBase::EnsureSlotCapacity(DynamicObject * instance, T increment /*= 1*/) { Assert(this->GetSlotCapacity() < MaxPropertyIndexSize); // Otherwise we can't grow this handler's capacity. We should've evolved to Bigger handler or OOM. // A Dictionary type is expected to have more properties // grow exponentially rather linearly to avoid the realloc and moves, // however use a small exponent to avoid waste - int newSlotCapacity = (nextPropertyIndex + 1); - newSlotCapacity += (newSlotCapacity>>2); + int newSlotCapacity = ::Math::Add(nextPropertyIndex, increment); + newSlotCapacity = ::Math::Add(newSlotCapacity, newSlotCapacity >> 2); if (newSlotCapacity > MaxPropertyIndexSize) { newSlotCapacity = MaxPropertyIndexSize; @@ -2102,7 +2102,7 @@ namespace Js this->EnsureSlotCapacity(instance); } - T index = nextPropertyIndex++; + T index = ::Math::PostInc(nextPropertyIndex); DictionaryPropertyDescriptor newDescriptor(index, attributes); // DictionaryTypeHandlers are not supposed to be shared. diff --git a/lib/Runtime/Types/DictionaryTypeHandler.h b/lib/Runtime/Types/DictionaryTypeHandler.h index 2e4974c6f99..f20c77cedb8 100644 --- a/lib/Runtime/Types/DictionaryTypeHandler.h +++ b/lib/Runtime/Types/DictionaryTypeHandler.h @@ -208,7 +208,7 @@ namespace Js void Add(const PropertyRecord* propertyId, PropertyAttributes attributes, ScriptContext *const scriptContext); void Add(const PropertyRecord* propertyId, PropertyAttributes attributes, bool isInitialized, bool isFixed, bool usedAsFixed, ScriptContext *const scriptContext); - void EnsureSlotCapacity(DynamicObject * instance); + void EnsureSlotCapacity(DynamicObject * instance, T increment = 1); BOOL AddProperty(DynamicObject* instance, const PropertyRecord* propertyRecord, Var value, PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, bool throwIfNotExtensible, SideEffects possibleSideEffects); ES5ArrayTypeHandlerBase* ConvertToES5ArrayType(DynamicObject *instance); diff --git a/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp b/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp index 24a442465de..211d660ba9b 100644 --- a/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp +++ b/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp @@ -1009,7 +1009,7 @@ namespace Js Assert(this->GetSlotCapacity() <= MaxPropertyIndexSize); // slotCapacity should never exceed MaxPropertyIndexSize Assert(nextPropertyIndex < this->GetSlotCapacity()); // nextPropertyIndex must be ready - Add(nextPropertyIndex++, propertyKey, attributes, isInitialized, isFixed, usedAsFixed, scriptContext); + Add(::Math::PostInc(nextPropertyIndex), propertyKey, attributes, isInitialized, isFixed, usedAsFixed, scriptContext); } template @@ -2563,8 +2563,8 @@ namespace Js // A Dictionary type is expected to have more properties // grow exponentially rather linearly to avoid the realloc and moves, // however use a small exponent to avoid waste - int newSlotCapacity = (nextPropertyIndex + 1); - newSlotCapacity += (newSlotCapacity>>2); + int newSlotCapacity = ::Math::Add(nextPropertyIndex, (TPropertyIndex)1); + newSlotCapacity = ::Math::Add(newSlotCapacity, newSlotCapacity >> 2); if (newSlotCapacity > MaxPropertyIndexSize) { newSlotCapacity = MaxPropertyIndexSize; diff --git a/test/AsmJs/lotsOfLocals.js b/test/AsmJs/lotsOfLocals.js new file mode 100644 index 00000000000..10d3a218dfc --- /dev/null +++ b/test/AsmJs/lotsOfLocals.js @@ -0,0 +1,18 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +const txt = ` + return function() { + "use asm"; + function foo() { + ${Array(50000).fill().map((_, i) => `var l${i} = 0.0;`).join("\n")} + } + return foo; + } +`; +const asmModule = (new Function(txt))(); +const asmFn = asmModule(); +asmFn(); +print("PASSED"); diff --git a/test/AsmJs/params.baseline b/test/AsmJs/params.baseline new file mode 100644 index 00000000000..f4cb23d263b --- /dev/null +++ b/test/AsmJs/params.baseline @@ -0,0 +1,2 @@ +Test(14000) +Module is invalid diff --git a/test/AsmJs/params.js b/test/AsmJs/params.js new file mode 100644 index 00000000000..03ba1e023b8 --- /dev/null +++ b/test/AsmJs/params.js @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft Corporation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +function* paramTypes() { + while (true) { + yield "i32"; + yield "f32"; + yield "f64"; + } +} + +function* callArguments() { + while (true) { + yield {value: -12345, i32: "-12345", f32: `fr(${Math.fround(-12345)})`, f64: "-12345.0"}; + yield {value: Math.PI, i32: `${Math.PI|0}`, f32: `fr(${Math.fround(Math.PI)})`, f64: `+${Math.PI}`}; + } +} + +function wrapType(type, name) { + switch (type) { + case "i32": return `${name}|0`; + case "f32": return `fr(${name})`; + case "f64": return `+${name}`; + default: throw new Error("Invalid Type"); + } +} + +const tested = {}; +function test(n) { + if (n in tested) { + return tested[n]; + } + print(`Test(${n})`); + const typeGen = paramTypes(); + const argGen = callArguments(); + const params = [], callArgs = [], verify = []; + for (let i = 0; i < n; ++i) { + const type = typeGen.next().value; + const paramName = `a${i|0}`; + const arg = argGen.next().value; + + params.push({type, name: paramName}); + callArgs.push(arg.value); + verify.push(`if (${wrapType(type, paramName)} != ${arg[type]}) return 1;`); + } + const paramList = params.map(p => p.name).join(","); + const paramsTypes = params.map(({type, name}) => `${name} = ${wrapType(type, name)}`).join("; "); + const txt = ` + return function AsmModule(stdlib) { + "use asm"; + var fr = stdlib.Math.fround; + function verify(${paramList}) { + ${paramsTypes}; + ${verify.join("\n")} + return 0; + } + function foo(${paramList}) { + ${paramsTypes} + return verify(${paramList})|0; + } + return foo; + }`; + + //print(txt); + try { + var AsmModule = (new Function(txt))(); + var foo = AsmModule({Math}); + if (foo(...callArgs) !== 1) { + print(`FAILED. Failed to validate with ${n} arguments`); + } + tested[n] = true; + return true; + } catch (e) { + } + tested[n] = false; +} +const [forceTest] = WScript.Arguments; +if (forceTest !== undefined) { + const res = test(forceTest); + print(res ? "Module is valid" : "Module is invalid"); + WScript.Quit(0); +} + +let nParams = 8201; +let inc = 100; +let direction = true; + +while (inc !== 0) { + if (test(nParams)) { + if (direction) { + nParams += inc; + } else { + direction = true; + inc >>= 1; + nParams += inc; + } + } else { + if (!direction) { + nParams -= inc; + } else { + direction = false; + inc >>= 1; + nParams -= inc; + } + } + + if (nParams > 100000 || nParams < 0) { + print(`FAILED. Params reached ${nParams} long. Expected an error by now`); + break; + } +} + +print(`Support at most ${nParams} params`); diff --git a/test/AsmJs/rlexe.xml b/test/AsmJs/rlexe.xml index 46a2baf2e35..150f85a5492 100644 --- a/test/AsmJs/rlexe.xml +++ b/test/AsmJs/rlexe.xml @@ -935,6 +935,22 @@ -asmjs -maic:0 + + + lotsOfLocals.js + exclude_chk + + + nested.js diff --git a/test/fieldopts/equiv-mismatch2.js b/test/fieldopts/equiv-mismatch2.js new file mode 100644 index 00000000000..86cf916da06 --- /dev/null +++ b/test/fieldopts/equiv-mismatch2.js @@ -0,0 +1,342 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft Corporation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +var shouldBailout = false; +var runningJITtedCode = false; +var reuseObjects = false; +var randomGenerator = function(inputseed) { + var seed = inputseed; + return function() { + // Robert Jenkins' 32 bit integer hash function. + seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; + seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; + seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; + seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; + seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; + seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; + return (seed & 0xfffffff) / 0x10000000; + }; +};; +var intArrayCreatorCount = 0; +function GenerateArray(seed, arrayType, arraySize, missingValuePercent, typeOfDeclaration) { + Math.random = randomGenerator(seed); + var result, codeToExecute, thisArrayName, maxMissingValues = Math.floor(arraySize * missingValuePercent), noOfMissingValuesAdded = 0; + var contents = []; + var isVarArray = arrayType == 'var'; + function IsMissingValue(allowedMissingValues) { + return Math.floor(Math.random() * 100) < allowedMissingValues + } + + thisArrayName = 'tempIntArr' + intArrayCreatorCount++; + + for (var arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { + if (isVarArray && arrayIndex != 0) { + arrayType = Math.floor(Math.random() * 100) < 50 ? 'int' : 'float'; + } + + if(noOfMissingValuesAdded < maxMissingValues && IsMissingValue(missingValuePercent)) { + noOfMissingValuesAdded++; + contents.push(''); + } else { + var randomValueGenerated; + if (arrayType == 'int') { + randomValueGenerated = Math.floor(Math.random() * seed); + } else if (arrayType == 'float') { + randomValueGenerated = Math.random() * seed; + } else if (arrayType == 'var') { + randomValueGenerated = '\'' + (Math.random() * seed).toString(36) + '\''; + } + contents.push(randomValueGenerated); + } + } + + if(contents.length == 1 && typeOfDeclaration == 'constructor') { + contents.push(Math.floor(Math.random() * seed)); + } + if(typeOfDeclaration == 'literal') { + codeToExecute = 'var ' + thisArrayName + ' = [' + contents.join() + '];'; + } else { + codeToExecute = 'var ' + thisArrayName + ' = new Array(' + contents.join() + ');'; + } + + codeToExecute += 'result = ' + thisArrayName + ';'; + eval(codeToExecute); + return result; +} +; + +function getRoundValue(n) { + if(n % 1 == 0) // int number + return n % 2147483647; + else // float number + return n.toFixed(8); + return n; +}; + +var print = WScript.Echo; +WScript.Echo = function(n) { /* suppress output to avoid huge baseline match but preserve the side-effects at call sites */ }; + +function formatOutput(n) {{ + return n.replace(/[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/g, function(match) {{return getRoundValue(parseFloat(match));}} ); +}}; +function sumOfArrayElements(prev, curr, index, array) { + return (typeof prev == "number" && typeof curr == "number") ? curr + prev : 0 +} +; +var __counter = 0; +function test0(){ + var loopInvariant = shouldBailout ? 0 : 5; + var GiantPrintArray = []; + __counter++;; + function makeArrayLength(x) { if(x < 1 || x > 4294967295 || x != x || isNaN(x) || !isFinite(x)) return 100; else return Math.floor(x) & 0xffff; };; + function leaf() { return 100; }; + var obj0 = {}; + var protoObj0 = {}; + var obj1 = {}; + var protoObj1 = {}; + var arrObj0 = {}; + var litObj0 = {prop1: 3.14159265358979}; + var litObj1 = {prop0: 0, prop1: 1}; + var arrObj0 = {}; + var func0 = function(argMath0,argMath1,argMath2 /*= argMath1*/){ + return (arrObj0.length |= (typeof(arrObj0.length) == 'undefined') ); + }; + var func1 = function(){ + return (((e >= this.prop1)||(f < protoObj1.prop0)) * (((obj0.prop1 !== protoObj1.length)||(arrObj0.length != g)) + +null)); + }; + var func2 = function(){ + strvar0 = strvar7[5%strvar7.length]; + arrObj0.prop1 /=((new RangeError()) instanceof ((typeof RegExp == 'function' ) ? RegExp : Object)); + var __loopvar1 = loopInvariant,__loopSecondaryVar1_0 = loopInvariant; + for (var _strvar0 in ary) { + if(typeof _strvar0 === 'string' && _strvar0.indexOf('method') != -1) continue; + __loopvar1 -= 2; + __loopSecondaryVar1_0--; + ary[_strvar0] = Math.pow((func2.caller), (protoObj0.prop0 += (((test0.caller) * ((func2.caller) - (func2.caller))) instanceof ((typeof Boolean == 'function' ) ? Boolean : Object)))); + (function(){ + })(); + _strvar0 = (func2.caller); + strvar4 = 'Ö$'+'+.'+'%!' + '}%'.concat(((obj0.length = func1()) instanceof ((typeof RegExp == 'function' ) ? RegExp : Object))); + } + return (((d /= func1.call(litObj1 )) * arrObj0[(((((shouldBailout ? (arrObj0[((((shouldBailout ? (Object.defineProperty(obj1, 'prop1', {writable: false, enumerable: true, configurable: true }), (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255])) : (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255]))) >= 0 ? ( (shouldBailout ? (Object.defineProperty(obj1, 'prop1', {writable: false, enumerable: true, configurable: true }), (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255])) : (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255]))) : 0) & 0xF)] = 'x') : undefined ), (shouldBailout ? (Object.defineProperty(obj1, 'prop1', {writable: false, enumerable: true, configurable: true }), (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255])) : (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255]))) >= 0 ? (shouldBailout ? (Object.defineProperty(obj1, 'prop1', {writable: false, enumerable: true, configurable: true }), (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255])) : (shouldBailout ? (h = { valueOf: function() { WScript.Echo('h valueOf'); return 3; } }, ui32[(f64[(protoObj1.length) & 255]) & 255]) : ui32[(f64[(protoObj1.length) & 255]) & 255])) : 0)) & 0XF)] - uic8.length) ? ((protoObj1.prop1 != g)||(obj1.prop1 != d)) : (shouldBailout ? (Object.defineProperty(arrObj0, 'prop0', {writable: true, enumerable: false, configurable: true }), arguments[((((b >>= ary[((((i8[(91) & 255] >> -1536822999.9) >= 0 ? (i8[(91) & 255] >> -1536822999.9) : 0)) & 0XF)]) >= 0 ? (b >>= ary[((((i8[(91) & 255] >> -1536822999.9) >= 0 ? (i8[(91) & 255] >> -1536822999.9) : 0)) & 0XF)]) : 0)) & 0XF)]) : arguments[((((b >>= ary[((((i8[(91) & 255] >> -1536822999.9) >= 0 ? (i8[(91) & 255] >> -1536822999.9) : 0)) & 0XF)]) >= 0 ? (b >>= ary[((((i8[(91) & 255] >> -1536822999.9) >= 0 ? (i8[(91) & 255] >> -1536822999.9) : 0)) & 0XF)]) : 0)) & 0XF)])); + }; + var func3 = function(argMath3,argMath4,...argArr5){ + var re1 = /^(?:(\d[a7]+\d)[a7]+(?!.))/g; + func1(); + return Math.abs(((({valueOf: function() { return 65537;}, prop7: i16[709111450], prop6: {valueOf: function() { return -2147483648;}, prop1: (d += 115)}, prop4: (obj0.prop1 /= (typeof(protoObj0.length) != 'undefined') ), prop3: (argMath4 === obj0.length), prop1: argMath4, 83: arrObj0[((((527182132 instanceof ((typeof EvalError == 'function' ) ? EvalError : Object)) >= 0 ? (527182132 instanceof ((typeof EvalError == 'function' ) ? EvalError : Object)) : 0)) & 0XF)], 62: ('1' + '^$!+'.charCodeAt((h)%7))} * argMath4 + ('%×Q!%' + '%,¢!;7%l' || (func3.caller))) && (typeof(protoObj0.prop0) != 'undefined') ) !== (typeof(f) != 'string') )); + }; + var func4 = function(){ + function func5 () { + } + obj6 = new func5(); + return (typeof(h) == 'number') ; + return (test0.caller); + }; + obj0.method0 = func3; + obj0.method1 = func2; + obj1.method0 = obj0.method0; + obj1.method1 = obj0.method0; + arrObj0.method0 = func3; + arrObj0.method1 = func3; + var ary = new Array(10); + var i8 = new Int8Array(256); + var i16 = new Int16Array(256); + var i32 = new Int32Array(256); + var ui8 = new Uint8Array(256); + var ui16 = new Uint16Array(256); + var ui32 = new Uint32Array(256); + var f32 = new Float32Array(256); + var f64 = new Float64Array(256); + var uic8 = new Uint8ClampedArray(256); + var IntArr0 = new Array(41,-840401897,2147483647,-737091287459698432,2073021661,-622597859,4294967296,4049808157226898432,-1073741824,-92706176,-73); + var IntArr1 = [6016909847483059200,-2147483646,-216446918726729152,-478410706549293760,-1415934990,-1462370209,-168]; + var FloatArr0 = new Array(); + var VarArr0 = new Array('#',-7.15739939346669E+18,-27344852,-210972656,-813745142,65536,1556601913,4294967297,7263023039494802432); + var a = 1252918647.1; + var b = 120; + var c = 2093313666; + var d = -4.00291011644038E+18; + var e = -256460535; + var f = true; + var g = 621525664; + var h = 2147483647; + var strvar0 = '1' + '^$!+'; + var strvar1 = '#' + '#!f!'; + var strvar2 = '#' + '#!f!'; + var strvar3 = 'Ðä'+'$o'+'$`' + '#a'; + var strvar4 = '±)K²$' + ')(6%$,f@'; + var strvar5 = 'Ðä'+'$o'+'$`' + '#a'; + var strvar6 = 'Ðä'+'$o'+'$`' + '#a'; + var strvar7 = 'F' + '!%H!'; + arrObj0[0] = +undefined; + arrObj0[1] = -559911196.9; + arrObj0[2] = 104; + arrObj0[3] = 946969434; + arrObj0[4] = +0; + arrObj0[5] = -81; + arrObj0[6] = undefined; + arrObj0[7] = -2147483649; + arrObj0[8] = 114; + arrObj0[9] = +null; + arrObj0[10] = 8.61626927758237E+18; + arrObj0[11] = 254; + arrObj0[12] = 237; + arrObj0[13] = 4.53231215898624E+18; + arrObj0[14] = +null; + arrObj0[arrObj0.length-1] = 87; + arrObj0.length = makeArrayLength(-240); + ary[0] = 352456150; + ary[1] = 1073741823; + ary[2] = -4.60776890876879E+18; + ary[3] = 214975552.1; + ary[4] = -204611709; + ary[5] = 759989774.1; + ary[6] = +Infinity; + ary[7] = -1032965232; + ary[8] = -1209001971.9; + ary[9] = -2147483648; + ary[10] = -411847650; + ary[11] = -2147483648; + ary[12] = 65536; + ary[13] = -1432897141.9; + ary[14] = 710489564; + ary[ary.length-1] = 166; + ary.length = makeArrayLength(+Infinity); + var protoObj0 = Object.create(obj0); + var protoObj1 = Object.create(obj1); + var aliasOfi16 = i16;; + this.prop0 = -3.28790484587579E+18; + this.prop1 = 317474964.1; + obj0.prop0 = 0; + obj0.prop1 = 5; + obj0.length = makeArrayLength(-267259884); + protoObj0.prop0 = 309791201; + protoObj0.prop1 = -2147483649; + protoObj0.length = makeArrayLength(-6.61778274392096E+17); + obj1.prop0 = 1346446687; + obj1.prop1 = +undefined; + obj1.length = makeArrayLength(612356277); + protoObj1.prop0 = -3; + protoObj1.prop1 = -5.42001747651146E+18; + protoObj1.length = makeArrayLength(8.17686394885755E+18); + arrObj0.prop0 = 1538442027.1; + arrObj0.prop1 = 3; + arrObj0.length = makeArrayLength(+null); + WScript.Echo(strvar3 >(test0.caller)); + strvar4 = strvar3[5%strvar3.length]; + with (protoObj1) { + strvar4 = strvar4.concat((test0.caller)); + WScript.Echo(strvar2 >=(({prop0: (~ (protoObj0.prop1 !== this.prop1)), prop1: (-811083942 instanceof ((typeof Function == 'function' ) ? Function : Object)), prop2: (-945286020 * ((-811083942 instanceof ((typeof Function == 'function' ) ? Function : Object)) - 5.0008137661454E+18)), prop3: ((shouldBailout ? (Object.defineProperty(obj0, 'length', {get: function() { WScript.Echo('obj0.length getter'); return 3; }, configurable: true }), -46168042) : -46168042) * (test0.caller) + (! 'Ðä'+'$o'+'$`' + '#a')), prop4: (shouldBailout ? (Object.defineProperty(obj0, 'length', {writable: false, enumerable: true, configurable: true }), Object.create({66: -98, prop0: null, prop1: '#', prop2: NaN, prop4: true, prop5: b, prop6: protoObj1.length, prop7: arrObj0.length})) : Object.create({66: -98, prop0: null, prop1: '#', prop2: NaN, prop4: true, prop5: b, prop6: protoObj1.length, prop7: arrObj0.length})), prop5: ((~ undefined) ? (-472897588 instanceof ((typeof Array == 'function' ) ? Array : Object)) : protoObj0.method0.call(protoObj1 , strvar2, FloatArr0, VarArr0)), prop6: f32[(protoObj1.method0(strvar5,IntArr0,FloatArr0)) & 255]} + (test0.caller)) ? arrObj0.length : {20: ((shouldBailout ? obj0.method1 = func2 : 1), obj0.method1()), 31: VarArr0[(5)], prop1: (shouldBailout ? (Object.defineProperty(this, 'prop0', {writable: false, enumerable: false, configurable: true }), ((g == this.prop0)&&(obj1.prop1 === d))) : ((g == this.prop0)&&(obj1.prop1 === d))), prop3: ary[(16)], prop4: ((- protoObj0.method1()) >= (shouldBailout ? (Object.defineProperty(protoObj1, 'prop1', {get: function() { WScript.Echo('protoObj1.prop1 getter'); return 3; }, set: function(_x) { WScript.Echo('protoObj1.prop1 setter'); }, configurable: true }), obj1.method1(strvar7,ary,VarArr0)) : obj1.method1(strvar7,ary,VarArr0))), prop6: ((obj1.prop1 <= obj0.prop1)||(obj1.length > arrObj0.prop1)), valueOf: function() { return 2147483650;}})); + obj6 = Object.create(protoObj0); + } + var __loopvar0 = loopInvariant,__loopSecondaryVar0_0 = loopInvariant; + do { + __loopSecondaryVar0_0 += 2; + if (__loopvar0 >= loopInvariant + 2) break; + __loopvar0++; + strvar6 = (',' + '%%Q*').replace('T', '1' + '^$!+') + arrObj0[((((((shouldBailout ? obj6.method0 = obj0.method0 : 1), obj6.method0(strvar5,IntArr0,FloatArr0)), h) >= 0 ? (((shouldBailout ? obj6.method0 = obj0.method0 : 1), obj6.method0(strvar5,IntArr0,FloatArr0)), h) : 0)) & 0XF)]; + strvar0 = strvar5.concat(obj1.prop1).concat((typeof(arrObj0.length) != 'string') ); + } while((i16[((((shouldBailout ? obj6.method0 = obj0.method0 : 1), obj6.method0(strvar5,IntArr0,FloatArr0)), h)) & 255])) + obj0.prop1={20: ((shouldBailout ? obj0.method1 = func2 : 1), obj0.method1()), 31: VarArr0[(5)], prop1: (shouldBailout ? (Object.defineProperty(this, 'prop0', {writable: false, enumerable: false, configurable: true }), ((g == this.prop0)&&(obj1.prop1 === d))) : ((g == this.prop0)&&(obj1.prop1 === d))), prop3: ary[(16)], prop4: ((- protoObj0.method1()) >= (shouldBailout ? (Object.defineProperty(protoObj1, 'prop1', {get: function() { WScript.Echo('protoObj1.prop1 getter'); return 3; }, set: function(_x) { WScript.Echo('protoObj1.prop1 setter'); }, configurable: true }), obj1.method1(strvar7,ary,VarArr0)) : obj1.method1(strvar7,ary,VarArr0))), prop6: ((obj1.prop1 <= obj0.prop1)||(obj1.length > arrObj0.prop1)), valueOf: function() { return 2147483650;}}; + WScript.Echo('a = ' + (a|0)); + WScript.Echo('b = ' + (b|0)); + WScript.Echo('c = ' + (c|0)); + WScript.Echo('d = ' + (d|0)); + WScript.Echo('e = ' + (e|0)); + WScript.Echo('f = ' + (f|0)); + WScript.Echo('g = ' + (g|0)); + WScript.Echo('h = ' + (h|0)); + WScript.Echo('this.prop0 = ' + (this.prop0|0)); + WScript.Echo('this.prop1 = ' + (this.prop1|0)); + WScript.Echo('obj0.prop0 = ' + (obj0.prop0|0)); + WScript.Echo('obj0.prop1 = ' + (obj0.prop1|0)); + WScript.Echo('obj0.length = ' + (obj0.length|0)); + WScript.Echo('protoObj0.prop0 = ' + (protoObj0.prop0|0)); + WScript.Echo('protoObj0.prop1 = ' + (protoObj0.prop1|0)); + WScript.Echo('protoObj0.length = ' + (protoObj0.length|0)); + WScript.Echo('obj1.prop0 = ' + (obj1.prop0|0)); + WScript.Echo('obj1.prop1 = ' + (obj1.prop1|0)); + WScript.Echo('obj1.length = ' + (obj1.length|0)); + WScript.Echo('protoObj1.prop0 = ' + (protoObj1.prop0|0)); + WScript.Echo('protoObj1.prop1 = ' + (protoObj1.prop1|0)); + WScript.Echo('protoObj1.length = ' + (protoObj1.length|0)); + WScript.Echo('arrObj0.prop0 = ' + (arrObj0.prop0|0)); + WScript.Echo('arrObj0.prop1 = ' + (arrObj0.prop1|0)); + WScript.Echo('arrObj0.length = ' + (arrObj0.length|0)); + WScript.Echo('obj6.prop0 = ' + (obj6.prop0|0)); + WScript.Echo('obj6.prop1 = ' + (obj6.prop1|0)); + WScript.Echo('obj6.length = ' + (obj6.length|0)); + WScript.Echo('strvar0 = ' + (strvar0)); + WScript.Echo('strvar1 = ' + (strvar1)); + WScript.Echo('strvar2 = ' + (strvar2)); + WScript.Echo('strvar3 = ' + (strvar3)); + WScript.Echo('strvar4 = ' + (strvar4)); + WScript.Echo('strvar5 = ' + (strvar5)); + WScript.Echo('strvar6 = ' + (strvar6)); + WScript.Echo('strvar7 = ' + (strvar7)); + WScript.Echo('arrObj0[0] = ' + (arrObj0[0]|0)); + WScript.Echo('arrObj0[1] = ' + (arrObj0[1]|0)); + WScript.Echo('arrObj0[2] = ' + (arrObj0[2]|0)); + WScript.Echo('arrObj0[3] = ' + (arrObj0[3]|0)); + WScript.Echo('arrObj0[4] = ' + (arrObj0[4]|0)); + WScript.Echo('arrObj0[5] = ' + (arrObj0[5]|0)); + WScript.Echo('arrObj0[6] = ' + (arrObj0[6]|0)); + WScript.Echo('arrObj0[7] = ' + (arrObj0[7]|0)); + WScript.Echo('arrObj0[8] = ' + (arrObj0[8]|0)); + WScript.Echo('arrObj0[9] = ' + (arrObj0[9]|0)); + WScript.Echo('arrObj0[10] = ' + (arrObj0[10]|0)); + WScript.Echo('arrObj0[11] = ' + (arrObj0[11]|0)); + WScript.Echo('arrObj0[12] = ' + (arrObj0[12]|0)); + WScript.Echo('arrObj0[13] = ' + (arrObj0[13]|0)); + WScript.Echo('arrObj0[14] = ' + (arrObj0[14]|0)); + WScript.Echo('arrObj0[arrObj0.length-1] = ' + (arrObj0[arrObj0.length-1]|0)); + WScript.Echo('arrObj0.length = ' + (arrObj0.length|0)); + WScript.Echo('ary[0] = ' + (ary[0]|0)); + WScript.Echo('ary[1] = ' + (ary[1]|0)); + WScript.Echo('ary[2] = ' + (ary[2]|0)); + WScript.Echo('ary[3] = ' + (ary[3]|0)); + WScript.Echo('ary[4] = ' + (ary[4]|0)); + WScript.Echo('ary[5] = ' + (ary[5]|0)); + WScript.Echo('ary[6] = ' + (ary[6]|0)); + WScript.Echo('ary[7] = ' + (ary[7]|0)); + WScript.Echo('ary[8] = ' + (ary[8]|0)); + WScript.Echo('ary[9] = ' + (ary[9]|0)); + WScript.Echo('ary[10] = ' + (ary[10]|0)); + WScript.Echo('ary[11] = ' + (ary[11]|0)); + WScript.Echo('ary[12] = ' + (ary[12]|0)); + WScript.Echo('ary[13] = ' + (ary[13]|0)); + WScript.Echo('ary[14] = ' + (ary[14]|0)); + WScript.Echo('ary[ary.length-1] = ' + (ary[ary.length-1]|0)); + WScript.Echo('ary.length = ' + (ary.length|0)); + for (var i = 0; i < GiantPrintArray.length; i++) { + WScript.Echo(GiantPrintArray[i]); + } +; + WScript.Echo('sumOfary = ' + ary.slice(0, 23).reduce(function(prev, curr) {{ return '' + prev + curr; }},0)); + WScript.Echo('subset_of_ary = ' + ary.slice(0, 11));; + WScript.Echo('sumOfIntArr0 = ' + IntArr0.slice(0, 23).reduce(function(prev, curr) {{ return '' + prev + curr; }},0)); + WScript.Echo('subset_of_IntArr0 = ' + IntArr0.slice(0, 11));; + WScript.Echo('sumOfIntArr1 = ' + IntArr1.slice(0, 23).reduce(function(prev, curr) {{ return '' + prev + curr; }},0)); + WScript.Echo('subset_of_IntArr1 = ' + IntArr1.slice(0, 11));; + WScript.Echo('sumOfFloatArr0 = ' + FloatArr0.slice(0, 23).reduce(function(prev, curr) {{ return '' + prev + curr; }},0)); + WScript.Echo('subset_of_FloatArr0 = ' + FloatArr0.slice(0, 11));; + WScript.Echo('sumOfVarArr0 = ' + VarArr0.slice(0, 23).reduce(function(prev, curr) {{ return '' + prev + curr; }},0)); + WScript.Echo('subset_of_VarArr0 = ' + VarArr0.slice(0, 11));; +}; + +// generate profile +test0(); +// Run Simple JIT +test0(); + +// run JITted code +runningJITtedCode = true; +test0(); + +// run code with bailouts enabled +shouldBailout = true; +test0(); + +print('pass'); diff --git a/test/fieldopts/rlexe.xml b/test/fieldopts/rlexe.xml index fae78b34f61..b7cc1bf6a99 100644 --- a/test/fieldopts/rlexe.xml +++ b/test/fieldopts/rlexe.xml @@ -19,6 +19,12 @@ equiv-mismatch.baseline + + + equiv-mismatch2.js + -force:rejit -off:bailonnoprofile + + equiv-locktypeid.js diff --git a/test/typedarray/reentry1.js b/test/typedarray/reentry1.js new file mode 100644 index 00000000000..0fff6c45105 --- /dev/null +++ b/test/typedarray/reentry1.js @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft Corporation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +function go(){ +a1 = [1.1,2.2] +a2 = [1.1,2.2] +ab = new ArrayBuffer(4) +tarr = new Uint8ClampedArray(ab) + +fakeaddr = 0xaaaabbbbbbbb * 4.9406564584124654E-324; +function aaa(p1,p2,ii){ + p1[0] = 1.1 + p1[1] = 2.2 + p2[0] = ii + p1[0] = fakeaddr + return ii +} + +function bbb(p1,p2,ii){ + p1[0] = 1.1 + p1[1] = 2.2 + p2[0] = ii + return p1[0] +} + +for(var i=0; i <0x100000; i++) { + aaa(a1,tarr,3) +} + +for(var i=0; i <0x100000; i++) { + bbb(a2,tarr,3) +} + +var arr = new Array( + 0x11111111,0x11111111,0x11111111,0x11111111,0x11111111, + 0x11111111,0x11111111,0x11111111,0x11111111,0x11111111, + 0x11111111,0x11111111,0x11111111,0x11111111,0x11111111 + ) +ab = new ArrayBuffer(0x100) +var farr = new Float64Array(ab) +var uarr = new Uint32Array(ab) + +farr[0] = bbb(a2, tarr, {toString:function(){a2[0] = arr; return 9}}) +var leakaddr = uarr[1]*0x100000000+uarr[0] + +fakeaddr = (leakaddr+0x58) * 4.9406564584124654E-324; +aaa(a1, tarr, {toString:function(){a1[0] = {}; return 9}}) +typeidaddr = leakaddr+0x58 +abaddr = leakaddr+0x2c + +function low32(v) +{ + return (v % 0x100000000); +} + +function high32(v) +{ + return Math.floor(v / 0x100000000); +} + +function toInt(v) +{ + return v < 0x80000000 ? v : -(0x100000000 - v); +} + +function toUint(v) +{ + return v >= 0 ? v : (0x100000000 + v); +} + +arr[0] = 56 +arr[1] = 0 +arr[2] = toInt(low32(typeidaddr)) +arr[3] = toInt(high32(typeidaddr)) +arr[4] = 0 +arr[5] = 0 +arr[6] = 0 +arr[7] = 0 +arr[8] = 0xabcd +arr[9] = 0 +arr[10] = toInt(low32(abaddr)) +arr[11] = toInt(high32(abaddr)) +arr[12] = 0 +arr[13] = 0 +arr[14] = 0x41414141 +arr[15] = 0x41414141 +arr[16] = 0 +arr[17] = 0 + +fakeobj = a1[0] + +var read32 = function(addr){ + arr[14] = toInt(low32(addr)) + arr[15] = toInt(high32(addr)) + return DataView.prototype.getUint32.call(fakeobj, 0, true) +} + +var write32 = function(addr, v){ + arr[14] = toInt(low32(addr)) + arr[15] = toInt(high32(addr)) + DataView.prototype.setUint32.call(fakeobj, 0, v, true) +} + +WScript.Echo("vtable:" + read32(leakaddr+4).toString(16) + read32(leakaddr).toString(16)) + +arr.length = 0xffffffff +write32(leakaddr+0x44, 0xffffffff) +write32(leakaddr+0x48, 0xffffffff) + +write32(0xaaaabbbbbbbb, 0) + +} +try{ +go()}catch(e){} + +WScript.Echo('pass'); diff --git a/test/typedarray/rlexe.xml b/test/typedarray/rlexe.xml index a1cf9ce6e77..8bfe8a69220 100644 --- a/test/typedarray/rlexe.xml +++ b/test/typedarray/rlexe.xml @@ -346,4 +346,9 @@ Below test fails with difference in space. Investigate the cause and re-enable t bug_OS_6911900.js + + + reentry1.js + + diff --git a/test/wasm/baselines/params.baseline b/test/wasm/baselines/params.baseline new file mode 100644 index 00000000000..f4cb23d263b --- /dev/null +++ b/test/wasm/baselines/params.baseline @@ -0,0 +1,2 @@ +Test(14000) +Module is invalid diff --git a/test/wasm/params.js b/test/wasm/params.js new file mode 100644 index 00000000000..a6d311c9557 --- /dev/null +++ b/test/wasm/params.js @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft Corporation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- +WScript.Flag("-wasmI64"); + +function* paramTypes() { + while(true) { + yield "i32"; + yield "i64"; + yield "f32"; + yield "f64"; + } +} + +function* callArguments() { + // 3 values to rotate which type will receive which arg + while(true) { + yield {value: -12345, i32: "(i32.const -12345)", i64: "(i64.const -12345)", f32: `(f32.const ${Math.fround(-12345)})`, f64: `(f64.const -12345.0)`}; + yield {value: "0xabcdef12345678", i32: "(i32.const 0)", i64: "(i64.const 0xabcdef12345678)", f32: "(f32.const 0)", f64: "(f64.const 0)"}; + yield {value: Math.PI, i32: `(i32.const ${Math.PI|0})`, i64: `(i64.const ${Math.PI|0})`, f32: `(f32.const ${Math.fround(Math.PI)})`, f64: `(f64.const ${Math.PI})`}; + } +} +const tested = {}; +function test(n) { + if (n in tested) { + return tested[n]; + } + print(`Test(${n})`) + const typeGen = paramTypes(); + const argGen = callArguments(); + let params = [], wasmCallTxt = [], callArgs = [], verify = []; + for (let i = 0; i < n; ++i) { + const type = typeGen.next().value; + const getLocal = `(get_local ${i|0})`; + const arg = argGen.next().value; + + params.push(type); + wasmCallTxt.push(getLocal); + callArgs.push(arg.value); + verify.push(`(${type}.ne ${getLocal} ${arg[type]}) (br_if 0 (i32.const 0)) (drop)`); + } + const paramsTxt = params.join(" "); + const txt = `(module + (func $verify (param ${paramsTxt}) (result i32) + ${verify.join("\n ")} + (i32.const 1) + ) + (func (export "foo") (param ${paramsTxt}) (result i32) + (call $verify + ${wasmCallTxt.join("\n ")} + ) + ) + )`; + //print(txt); + const buf = WebAssembly.wabt.convertWast2Wasm(txt); + try { + const module = new WebAssembly.Module(buf); + const {exports: {foo}} = new WebAssembly.Instance(module); + if (foo(...callArgs) !== 1) { + print(`FAILED. Failed to validate with ${n} arguments`); + } + tested[n] = true; + return true; + } catch (e) { + } + tested[n] = false; +} + +const [forceTest] = WScript.Arguments; +if (forceTest !== undefined) { + const res = test(forceTest); + print(res ? "Module is valid" : "Module is invalid"); + WScript.Quit(0); +} + +let nParams = 9000; +let inc = 1000; +let direction = true; + +while (inc !== 0) { + if (test(nParams)) { + if (direction) { + nParams += inc; + } else { + direction = true; + inc >>= 1; + nParams += inc; + } + } else { + if (!direction) { + nParams -= inc; + } else { + direction = false; + inc >>= 1; + // make sure the last test is a passing one + inc = inc || 1; + nParams -= inc; + } + } + + if (nParams > 100000 || nParams < 0) { + print(`FAILED. Params reached ${nParams} long. Expected an error by now`); + break; + } +} + +print(`Support at most ${nParams} params`); diff --git a/test/wasm/rlexe.xml b/test/wasm/rlexe.xml index 778eab443e9..959f9e9bba6 100644 --- a/test/wasm/rlexe.xml +++ b/test/wasm/rlexe.xml @@ -158,6 +158,16 @@ exclude_jshost,exclude_win7 + debugger.js