diff options
Diffstat (limited to 'js/src/jit/MCallOptimize.cpp')
-rw-r--r-- | js/src/jit/MCallOptimize.cpp | 4099 |
1 files changed, 4099 insertions, 0 deletions
diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp new file mode 100644 index 000000000..202aef497 --- /dev/null +++ b/js/src/jit/MCallOptimize.cpp @@ -0,0 +1,4099 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Casting.h" + +#include "jsmath.h" +#include "jsobj.h" +#include "jsstr.h" + +#include "builtin/AtomicsObject.h" +#include "builtin/SIMD.h" +#include "builtin/TestingFunctions.h" +#include "builtin/TypedObject.h" +#include "jit/BaselineInspector.h" +#include "jit/InlinableNatives.h" +#include "jit/IonBuilder.h" +#include "jit/Lowering.h" +#include "jit/MIR.h" +#include "jit/MIRGraph.h" +#include "vm/ArgumentsObject.h" +#include "vm/ProxyObject.h" +#include "vm/SelfHosting.h" +#include "vm/TypedArrayObject.h" + +#include "jsscriptinlines.h" + +#include "jit/shared/Lowering-shared-inl.h" +#include "vm/NativeObject-inl.h" +#include "vm/StringObject-inl.h" +#include "vm/UnboxedObject-inl.h" + +using mozilla::ArrayLength; +using mozilla::AssertedCast; + +using JS::DoubleNaNValue; +using JS::TrackedOutcome; +using JS::TrackedStrategy; +using JS::TrackedTypeSite; + +namespace js { +namespace jit { + +IonBuilder::InliningStatus +IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) +{ + MOZ_ASSERT(target->isNative()); + + if (!optimizationInfo().inlineNative()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon); + return InliningStatus_NotInlined; + } + + if (!target->jitInfo() || target->jitInfo()->type() != JSJitInfo::InlinableNative) { + // Reaching here means we tried to inline a native for which there is no + // Ion specialization. + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeNoSpecialization); + return InliningStatus_NotInlined; + } + + // Default failure reason is observing an unsupported type. + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadType); + + if (shouldAbortOnPreliminaryGroups(callInfo.thisArg())) + return InliningStatus_NotInlined; + for (size_t i = 0; i < callInfo.argc(); i++) { + if (shouldAbortOnPreliminaryGroups(callInfo.getArg(i))) + return InliningStatus_NotInlined; + } + + switch (InlinableNative inlNative = target->jitInfo()->inlinableNative) { + // Array natives. + case InlinableNative::Array: + return inlineArray(callInfo); + case InlinableNative::ArrayIsArray: + return inlineArrayIsArray(callInfo); + case InlinableNative::ArrayJoin: + return inlineArrayJoin(callInfo); + case InlinableNative::ArrayPop: + return inlineArrayPopShift(callInfo, MArrayPopShift::Pop); + case InlinableNative::ArrayShift: + return inlineArrayPopShift(callInfo, MArrayPopShift::Shift); + case InlinableNative::ArrayPush: + return inlineArrayPush(callInfo); + case InlinableNative::ArraySlice: + return inlineArraySlice(callInfo); + case InlinableNative::ArraySplice: + return inlineArraySplice(callInfo); + + // Atomic natives. + case InlinableNative::AtomicsCompareExchange: + return inlineAtomicsCompareExchange(callInfo); + case InlinableNative::AtomicsExchange: + return inlineAtomicsExchange(callInfo); + case InlinableNative::AtomicsLoad: + return inlineAtomicsLoad(callInfo); + case InlinableNative::AtomicsStore: + return inlineAtomicsStore(callInfo); + case InlinableNative::AtomicsAdd: + case InlinableNative::AtomicsSub: + case InlinableNative::AtomicsAnd: + case InlinableNative::AtomicsOr: + case InlinableNative::AtomicsXor: + return inlineAtomicsBinop(callInfo, inlNative); + case InlinableNative::AtomicsIsLockFree: + return inlineAtomicsIsLockFree(callInfo); + + // Math natives. + case InlinableNative::MathAbs: + return inlineMathAbs(callInfo); + case InlinableNative::MathFloor: + return inlineMathFloor(callInfo); + case InlinableNative::MathCeil: + return inlineMathCeil(callInfo); + case InlinableNative::MathRound: + return inlineMathRound(callInfo); + case InlinableNative::MathClz32: + return inlineMathClz32(callInfo); + case InlinableNative::MathSqrt: + return inlineMathSqrt(callInfo); + case InlinableNative::MathATan2: + return inlineMathAtan2(callInfo); + case InlinableNative::MathHypot: + return inlineMathHypot(callInfo); + case InlinableNative::MathMax: + return inlineMathMinMax(callInfo, true /* max */); + case InlinableNative::MathMin: + return inlineMathMinMax(callInfo, false /* max */); + case InlinableNative::MathPow: + return inlineMathPow(callInfo); + case InlinableNative::MathRandom: + return inlineMathRandom(callInfo); + case InlinableNative::MathImul: + return inlineMathImul(callInfo); + case InlinableNative::MathFRound: + return inlineMathFRound(callInfo); + case InlinableNative::MathSin: + return inlineMathFunction(callInfo, MMathFunction::Sin); + case InlinableNative::MathTan: + return inlineMathFunction(callInfo, MMathFunction::Tan); + case InlinableNative::MathCos: + return inlineMathFunction(callInfo, MMathFunction::Cos); + case InlinableNative::MathExp: + return inlineMathFunction(callInfo, MMathFunction::Exp); + case InlinableNative::MathLog: + return inlineMathFunction(callInfo, MMathFunction::Log); + case InlinableNative::MathASin: + return inlineMathFunction(callInfo, MMathFunction::ASin); + case InlinableNative::MathATan: + return inlineMathFunction(callInfo, MMathFunction::ATan); + case InlinableNative::MathACos: + return inlineMathFunction(callInfo, MMathFunction::ACos); + case InlinableNative::MathLog10: + return inlineMathFunction(callInfo, MMathFunction::Log10); + case InlinableNative::MathLog2: + return inlineMathFunction(callInfo, MMathFunction::Log2); + case InlinableNative::MathLog1P: + return inlineMathFunction(callInfo, MMathFunction::Log1P); + case InlinableNative::MathExpM1: + return inlineMathFunction(callInfo, MMathFunction::ExpM1); + case InlinableNative::MathCosH: + return inlineMathFunction(callInfo, MMathFunction::CosH); + case InlinableNative::MathSinH: + return inlineMathFunction(callInfo, MMathFunction::SinH); + case InlinableNative::MathTanH: + return inlineMathFunction(callInfo, MMathFunction::TanH); + case InlinableNative::MathACosH: + return inlineMathFunction(callInfo, MMathFunction::ACosH); + case InlinableNative::MathASinH: + return inlineMathFunction(callInfo, MMathFunction::ASinH); + case InlinableNative::MathATanH: + return inlineMathFunction(callInfo, MMathFunction::ATanH); + case InlinableNative::MathSign: + return inlineMathFunction(callInfo, MMathFunction::Sign); + case InlinableNative::MathTrunc: + return inlineMathFunction(callInfo, MMathFunction::Trunc); + case InlinableNative::MathCbrt: + return inlineMathFunction(callInfo, MMathFunction::Cbrt); + + // RegExp natives. + case InlinableNative::RegExpMatcher: + return inlineRegExpMatcher(callInfo); + case InlinableNative::RegExpSearcher: + return inlineRegExpSearcher(callInfo); + case InlinableNative::RegExpTester: + return inlineRegExpTester(callInfo); + case InlinableNative::IsRegExpObject: + return inlineIsRegExpObject(callInfo); + case InlinableNative::RegExpPrototypeOptimizable: + return inlineRegExpPrototypeOptimizable(callInfo); + case InlinableNative::RegExpInstanceOptimizable: + return inlineRegExpInstanceOptimizable(callInfo); + case InlinableNative::GetFirstDollarIndex: + return inlineGetFirstDollarIndex(callInfo); + + // String natives. + case InlinableNative::String: + return inlineStringObject(callInfo); + case InlinableNative::StringCharCodeAt: + return inlineStrCharCodeAt(callInfo); + case InlinableNative::StringFromCharCode: + return inlineStrFromCharCode(callInfo); + case InlinableNative::StringFromCodePoint: + return inlineStrFromCodePoint(callInfo); + case InlinableNative::StringCharAt: + return inlineStrCharAt(callInfo); + + // String intrinsics. + case InlinableNative::IntrinsicStringReplaceString: + return inlineStringReplaceString(callInfo); + case InlinableNative::IntrinsicStringSplitString: + return inlineStringSplitString(callInfo); + + // Object natives. + case InlinableNative::ObjectCreate: + return inlineObjectCreate(callInfo); + + // SIMD natives. + case InlinableNative::SimdInt32x4: + return inlineSimd(callInfo, target, SimdType::Int32x4); + case InlinableNative::SimdUint32x4: + return inlineSimd(callInfo, target, SimdType::Uint32x4); + case InlinableNative::SimdInt16x8: + return inlineSimd(callInfo, target, SimdType::Int16x8); + case InlinableNative::SimdUint16x8: + return inlineSimd(callInfo, target, SimdType::Uint16x8); + case InlinableNative::SimdInt8x16: + return inlineSimd(callInfo, target, SimdType::Int8x16); + case InlinableNative::SimdUint8x16: + return inlineSimd(callInfo, target, SimdType::Uint8x16); + case InlinableNative::SimdFloat32x4: + return inlineSimd(callInfo, target, SimdType::Float32x4); + case InlinableNative::SimdBool32x4: + return inlineSimd(callInfo, target, SimdType::Bool32x4); + case InlinableNative::SimdBool16x8: + return inlineSimd(callInfo, target, SimdType::Bool16x8); + case InlinableNative::SimdBool8x16: + return inlineSimd(callInfo, target, SimdType::Bool8x16); + + // Testing functions. + case InlinableNative::TestBailout: + return inlineBailout(callInfo); + case InlinableNative::TestAssertFloat32: + return inlineAssertFloat32(callInfo); + case InlinableNative::TestAssertRecoveredOnBailout: + return inlineAssertRecoveredOnBailout(callInfo); + + // Slot intrinsics. + case InlinableNative::IntrinsicUnsafeSetReservedSlot: + return inlineUnsafeSetReservedSlot(callInfo); + case InlinableNative::IntrinsicUnsafeGetReservedSlot: + return inlineUnsafeGetReservedSlot(callInfo, MIRType::Value); + case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot: + return inlineUnsafeGetReservedSlot(callInfo, MIRType::Object); + case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot: + return inlineUnsafeGetReservedSlot(callInfo, MIRType::Int32); + case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot: + return inlineUnsafeGetReservedSlot(callInfo, MIRType::String); + case InlinableNative::IntrinsicUnsafeGetBooleanFromReservedSlot: + return inlineUnsafeGetReservedSlot(callInfo, MIRType::Boolean); + + // Utility intrinsics. + case InlinableNative::IntrinsicIsCallable: + return inlineIsCallable(callInfo); + case InlinableNative::IntrinsicIsConstructor: + return inlineIsConstructor(callInfo); + case InlinableNative::IntrinsicToObject: + return inlineToObject(callInfo); + case InlinableNative::IntrinsicIsObject: + return inlineIsObject(callInfo); + case InlinableNative::IntrinsicIsWrappedArrayConstructor: + return inlineIsWrappedArrayConstructor(callInfo); + case InlinableNative::IntrinsicToInteger: + return inlineToInteger(callInfo); + case InlinableNative::IntrinsicToString: + return inlineToString(callInfo); + case InlinableNative::IntrinsicIsConstructing: + return inlineIsConstructing(callInfo); + case InlinableNative::IntrinsicSubstringKernel: + return inlineSubstringKernel(callInfo); + case InlinableNative::IntrinsicIsArrayIterator: + return inlineHasClass(callInfo, &ArrayIteratorObject::class_); + case InlinableNative::IntrinsicIsMapIterator: + return inlineHasClass(callInfo, &MapIteratorObject::class_); + case InlinableNative::IntrinsicIsSetIterator: + return inlineHasClass(callInfo, &SetIteratorObject::class_); + case InlinableNative::IntrinsicIsStringIterator: + return inlineHasClass(callInfo, &StringIteratorObject::class_); + case InlinableNative::IntrinsicIsListIterator: + return inlineHasClass(callInfo, &ListIteratorObject::class_); + case InlinableNative::IntrinsicDefineDataProperty: + return inlineDefineDataProperty(callInfo); + case InlinableNative::IntrinsicObjectHasPrototype: + return inlineObjectHasPrototype(callInfo); + + // Map intrinsics. + case InlinableNative::IntrinsicGetNextMapEntryForIterator: + return inlineGetNextEntryForIterator(callInfo, MGetNextEntryForIterator::Map); + + // Set intrinsics. + case InlinableNative::IntrinsicGetNextSetEntryForIterator: + return inlineGetNextEntryForIterator(callInfo, MGetNextEntryForIterator::Set); + + // ArrayBuffer intrinsics. + case InlinableNative::IntrinsicArrayBufferByteLength: + return inlineArrayBufferByteLength(callInfo); + case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength: + return inlinePossiblyWrappedArrayBufferByteLength(callInfo); + + // TypedArray intrinsics. + case InlinableNative::TypedArrayConstructor: + return inlineTypedArray(callInfo, target->native()); + case InlinableNative::IntrinsicIsTypedArray: + return inlineIsTypedArray(callInfo); + case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray: + return inlineIsPossiblyWrappedTypedArray(callInfo); + case InlinableNative::IntrinsicPossiblyWrappedTypedArrayLength: + return inlinePossiblyWrappedTypedArrayLength(callInfo); + case InlinableNative::IntrinsicTypedArrayLength: + return inlineTypedArrayLength(callInfo); + case InlinableNative::IntrinsicSetDisjointTypedElements: + return inlineSetDisjointTypedElements(callInfo); + + // TypedObject intrinsics. + case InlinableNative::IntrinsicObjectIsTypedObject: + return inlineHasClass(callInfo, + &OutlineTransparentTypedObject::class_, + &OutlineOpaqueTypedObject::class_, + &InlineTransparentTypedObject::class_, + &InlineOpaqueTypedObject::class_); + case InlinableNative::IntrinsicObjectIsTransparentTypedObject: + return inlineHasClass(callInfo, + &OutlineTransparentTypedObject::class_, + &InlineTransparentTypedObject::class_); + case InlinableNative::IntrinsicObjectIsOpaqueTypedObject: + return inlineHasClass(callInfo, + &OutlineOpaqueTypedObject::class_, + &InlineOpaqueTypedObject::class_); + case InlinableNative::IntrinsicObjectIsTypeDescr: + return inlineObjectIsTypeDescr(callInfo); + case InlinableNative::IntrinsicTypeDescrIsSimpleType: + return inlineHasClass(callInfo, + &ScalarTypeDescr::class_, &ReferenceTypeDescr::class_); + case InlinableNative::IntrinsicTypeDescrIsArrayType: + return inlineHasClass(callInfo, &ArrayTypeDescr::class_); + case InlinableNative::IntrinsicSetTypedObjectOffset: + return inlineSetTypedObjectOffset(callInfo); + } + + MOZ_CRASH("Shouldn't get here"); +} + +IonBuilder::InliningStatus +IonBuilder::inlineNativeGetter(CallInfo& callInfo, JSFunction* target) +{ + MOZ_ASSERT(target->isNative()); + JSNative native = target->native(); + + if (!optimizationInfo().inlineNative()) + return InliningStatus_NotInlined; + + MDefinition* thisArg = callInfo.thisArg(); + TemporaryTypeSet* thisTypes = thisArg->resultTypeSet(); + MOZ_ASSERT(callInfo.argc() == 0); + + if (!thisTypes) + return InliningStatus_NotInlined; + + // Try to optimize typed array lengths. + if (TypedArrayObject::isOriginalLengthGetter(native)) { + Scalar::Type type = thisTypes->getTypedArrayType(constraints()); + if (type == Scalar::MaxTypedArrayViewType) + return InliningStatus_NotInlined; + + MInstruction* length = addTypedArrayLength(thisArg); + current->push(length); + return InliningStatus_Inlined; + } + + // Try to optimize RegExp getters. + RegExpFlag mask = NoFlags; + if (RegExpObject::isOriginalFlagGetter(native, &mask)) { + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &RegExpObject::class_) + return InliningStatus_NotInlined; + + MLoadFixedSlot* flags = MLoadFixedSlot::New(alloc(), thisArg, RegExpObject::flagsSlot()); + current->add(flags); + flags->setResultType(MIRType::Int32); + MConstant* maskConst = MConstant::New(alloc(), Int32Value(mask)); + current->add(maskConst); + MBitAnd* maskedFlag = MBitAnd::New(alloc(), flags, maskConst); + maskedFlag->setInt32Specialization(); + current->add(maskedFlag); + + MDefinition* result = convertToBoolean(maskedFlag); + current->push(result); + return InliningStatus_Inlined; + } + + return InliningStatus_NotInlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineNonFunctionCall(CallInfo& callInfo, JSObject* target) +{ + // Inline a call to a non-function object, invoking the object's call or + // construct hook. + + if (callInfo.constructing() && target->constructHook() == TypedObject::construct) + return inlineConstructTypedObject(callInfo, &target->as<TypeDescr>()); + + if (!callInfo.constructing() && target->callHook() == SimdTypeDescr::call) + return inlineConstructSimdObject(callInfo, &target->as<SimdTypeDescr>()); + + return InliningStatus_NotInlined; +} + +TemporaryTypeSet* +IonBuilder::getInlineReturnTypeSet() +{ + return bytecodeTypes(pc); +} + +MIRType +IonBuilder::getInlineReturnType() +{ + TemporaryTypeSet* returnTypes = getInlineReturnTypeSet(); + return returnTypes->getKnownMIRType(); +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathFunction(CallInfo& callInfo, MMathFunction::Function function) +{ + if (callInfo.constructing()) + return InliningStatus_NotInlined; + + if (callInfo.argc() != 1) + return InliningStatus_NotInlined; + + if (getInlineReturnType() != MIRType::Double) + return InliningStatus_NotInlined; + if (!IsNumberType(callInfo.getArg(0)->type())) + return InliningStatus_NotInlined; + + const MathCache* cache = GetJSContextFromMainThread()->caches.maybeGetMathCache(); + + callInfo.fun()->setImplicitlyUsedUnchecked(); + callInfo.thisArg()->setImplicitlyUsedUnchecked(); + + MMathFunction* ins = MMathFunction::New(alloc(), callInfo.getArg(0), function, cache); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArray(CallInfo& callInfo) +{ + uint32_t initLength = 0; + + JSObject* templateObject = inspector->getTemplateObjectForNative(pc, ArrayConstructor); + // This is shared by ArrayConstructor and array_construct (std_Array). + if (!templateObject) + templateObject = inspector->getTemplateObjectForNative(pc, array_construct); + + if (!templateObject) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeNoTemplateObj); + return InliningStatus_NotInlined; + } + + if (templateObject->is<UnboxedArrayObject>()) { + if (templateObject->group()->unboxedLayout().nativeGroup()) + return InliningStatus_NotInlined; + } + + // Multiple arguments imply array initialization, not just construction. + if (callInfo.argc() >= 2) { + initLength = callInfo.argc(); + + TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(templateObject); + if (!key->unknownProperties()) { + HeapTypeSetKey elemTypes = key->property(JSID_VOID); + + for (uint32_t i = 0; i < initLength; i++) { + MDefinition* value = callInfo.getArg(i); + if (!TypeSetIncludes(elemTypes.maybeTypes(), value->type(), value->resultTypeSet())) { + elemTypes.freeze(constraints()); + return InliningStatus_NotInlined; + } + } + } + } + + // A single integer argument denotes initial length. + if (callInfo.argc() == 1) { + MDefinition* arg = callInfo.getArg(0); + if (arg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + if (!arg->isConstant()) { + callInfo.setImplicitlyUsedUnchecked(); + MNewArrayDynamicLength* ins = + MNewArrayDynamicLength::New(alloc(), constraints(), templateObject, + templateObject->group()->initialHeap(constraints()), + arg); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + // The next several checks all may fail due to range conditions. + trackOptimizationOutcome(TrackedOutcome::ArrayRange); + + // Negative lengths generate a RangeError, unhandled by the inline path. + initLength = arg->toConstant()->toInt32(); + if (initLength > NativeObject::MAX_DENSE_ELEMENTS_COUNT) + return InliningStatus_NotInlined; + MOZ_ASSERT(initLength <= INT32_MAX); + + // Make sure initLength matches the template object's length. This is + // not guaranteed to be the case, for instance if we're inlining the + // MConstant may come from an outer script. + if (initLength != GetAnyBoxedOrUnboxedArrayLength(templateObject)) + return InliningStatus_NotInlined; + + // Don't inline large allocations. + if (initLength > ArrayObject::EagerAllocationMaxLength) + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + if (!jsop_newarray(templateObject, initLength)) + return InliningStatus_Error; + + MDefinition* array = current->peek(-1); + if (callInfo.argc() >= 2) { + JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject); + for (uint32_t i = 0; i < initLength; i++) { + if (!alloc().ensureBallast()) + return InliningStatus_Error; + MDefinition* value = callInfo.getArg(i); + if (!initializeArrayElement(array, i, value, unboxedType, /* addResumePoint = */ false)) + return InliningStatus_Error; + } + + MInstruction* setLength = setInitializedLength(array, unboxedType, initLength); + if (!resumeAfter(setLength)) + return InliningStatus_Error; + } + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArrayIsArray(CallInfo& callInfo) +{ + if (callInfo.constructing() || callInfo.argc() != 1) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + MDefinition* arg = callInfo.getArg(0); + + bool isArray; + if (!arg->mightBeType(MIRType::Object)) { + isArray = false; + } else { + if (arg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* types = arg->resultTypeSet(); + const Class* clasp = types ? types->getKnownClass(constraints()) : nullptr; + if (!clasp || clasp->isProxy()) + return InliningStatus_NotInlined; + + isArray = (clasp == &ArrayObject::class_ || clasp == &UnboxedArrayObject::class_); + } + + pushConstant(BooleanValue(isArray)); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode) +{ + if (callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType returnType = getInlineReturnType(); + if (returnType == MIRType::Undefined || returnType == MIRType::Null) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType::Object) + return InliningStatus_NotInlined; + + // Pop and shift are only handled for dense arrays that have never been + // used in an iterator: popping elements does not account for suppressing + // deleted properties in active iterators. + ObjectGroupFlags unhandledFlags = + OBJECT_FLAG_SPARSE_INDEXES | + OBJECT_FLAG_LENGTH_OVERFLOW | + OBJECT_FLAG_ITERATED; + + MDefinition* obj = convertUnboxedObjects(callInfo.thisArg()); + TemporaryTypeSet* thisTypes = obj->resultTypeSet(); + if (!thisTypes) + return InliningStatus_NotInlined; + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) + return InliningStatus_NotInlined; + if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) { + trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags); + return InliningStatus_NotInlined; + } + + if (ArrayPrototypeHasIndexedProperty(this, script())) { + trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); + return InliningStatus_NotInlined; + } + + JSValueType unboxedType = JSVAL_TYPE_MAGIC; + if (clasp == &UnboxedArrayObject::class_) { + unboxedType = UnboxedArrayElementType(constraints(), obj, nullptr); + if (unboxedType == JSVAL_TYPE_MAGIC) + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + if (clasp == &ArrayObject::class_) + obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); + + TemporaryTypeSet* returnTypes = getInlineReturnTypeSet(); + bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED); + bool maybeUndefined = returnTypes->hasType(TypeSet::UndefinedType()); + + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), + obj, nullptr, returnTypes); + if (barrier != BarrierKind::NoBarrier) + returnType = MIRType::Value; + + MArrayPopShift* ins = MArrayPopShift::New(alloc(), obj, mode, + unboxedType, needsHoleCheck, maybeUndefined); + current->add(ins); + current->push(ins); + ins->setResultType(returnType); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + + if (!pushTypeBarrier(ins, returnTypes, barrier)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArraySplice(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // Ensure |this|, argument and result are objects. + if (getInlineReturnType() != MIRType::Object) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + if (callInfo.getArg(1)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + // Specialize arr.splice(start, deleteCount) with unused return value and + // avoid creating the result array in this case. + if (!BytecodeIsPopped(pc)) { + trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); + return InliningStatus_NotInlined; + } + + MArraySplice* ins = MArraySplice::New(alloc(), + callInfo.thisArg(), + callInfo.getArg(0), + callInfo.getArg(1)); + + current->add(ins); + pushConstant(UndefinedValue()); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArrayJoin(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::String) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType::String) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MArrayJoin* ins = MArrayJoin::New(alloc(), callInfo.thisArg(), callInfo.getArg(0)); + + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArrayPush(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* obj = convertUnboxedObjects(callInfo.thisArg()); + MDefinition* value = callInfo.getArg(0); + if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, + &obj, nullptr, &value, /* canModify = */ false)) + { + trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Int32) + return InliningStatus_NotInlined; + if (obj->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* thisTypes = obj->resultTypeSet(); + if (!thisTypes) + return InliningStatus_NotInlined; + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) + return InliningStatus_NotInlined; + if (thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES | + OBJECT_FLAG_LENGTH_OVERFLOW)) + { + trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags); + return InliningStatus_NotInlined; + } + + if (ArrayPrototypeHasIndexedProperty(this, script())) { + trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); + return InliningStatus_NotInlined; + } + + TemporaryTypeSet::DoubleConversion conversion = + thisTypes->convertDoubleElements(constraints()); + if (conversion == TemporaryTypeSet::AmbiguousDoubleConversion) { + trackOptimizationOutcome(TrackedOutcome::ArrayDoubleConversion); + return InliningStatus_NotInlined; + } + + JSValueType unboxedType = JSVAL_TYPE_MAGIC; + if (clasp == &UnboxedArrayObject::class_) { + unboxedType = UnboxedArrayElementType(constraints(), obj, nullptr); + if (unboxedType == JSVAL_TYPE_MAGIC) + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles || + conversion == TemporaryTypeSet::MaybeConvertToDoubles) + { + MInstruction* valueDouble = MToDouble::New(alloc(), value); + current->add(valueDouble); + value = valueDouble; + } + + if (unboxedType == JSVAL_TYPE_MAGIC) + obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); + + if (NeedsPostBarrier(value)) + current->add(MPostWriteBarrier::New(alloc(), obj, value)); + + MArrayPush* ins = MArrayPush::New(alloc(), obj, value, unboxedType); + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArraySlice(CallInfo& callInfo) +{ + if (callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* obj = convertUnboxedObjects(callInfo.thisArg()); + + // Ensure |this| and result are objects. + if (getInlineReturnType() != MIRType::Object) + return InliningStatus_NotInlined; + if (obj->type() != MIRType::Object) + return InliningStatus_NotInlined; + + // Arguments for the sliced region must be integers. + if (callInfo.argc() > 0) { + if (callInfo.getArg(0)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + if (callInfo.argc() > 1) { + if (callInfo.getArg(1)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + } + } + + // |this| must be a dense array. + TemporaryTypeSet* thisTypes = obj->resultTypeSet(); + if (!thisTypes) + return InliningStatus_NotInlined; + + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) + return InliningStatus_NotInlined; + if (thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES | + OBJECT_FLAG_LENGTH_OVERFLOW)) + { + trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags); + return InliningStatus_NotInlined; + } + + JSValueType unboxedType = JSVAL_TYPE_MAGIC; + if (clasp == &UnboxedArrayObject::class_) { + unboxedType = UnboxedArrayElementType(constraints(), obj, nullptr); + if (unboxedType == JSVAL_TYPE_MAGIC) + return InliningStatus_NotInlined; + } + + // Watch out for indexed properties on the prototype. + if (ArrayPrototypeHasIndexedProperty(this, script())) { + trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); + return InliningStatus_NotInlined; + } + + // The group of the result will be dynamically fixed up to match the input + // object, allowing us to handle 'this' objects that might have more than + // one group. Make sure that no singletons can be sliced here. + for (unsigned i = 0; i < thisTypes->getObjectCount(); i++) { + TypeSet::ObjectKey* key = thisTypes->getObject(i); + if (key && key->isSingleton()) + return InliningStatus_NotInlined; + } + + // Inline the call. + JSObject* templateObj = inspector->getTemplateObjectForNative(pc, js::array_slice); + if (!templateObj) + return InliningStatus_NotInlined; + + if (unboxedType == JSVAL_TYPE_MAGIC) { + if (!templateObj->is<ArrayObject>()) + return InliningStatus_NotInlined; + } else { + if (!templateObj->is<UnboxedArrayObject>()) + return InliningStatus_NotInlined; + if (templateObj->as<UnboxedArrayObject>().elementType() != unboxedType) + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + MDefinition* begin; + if (callInfo.argc() > 0) + begin = callInfo.getArg(0); + else + begin = constant(Int32Value(0)); + + MDefinition* end; + if (callInfo.argc() > 1) { + end = callInfo.getArg(1); + } else if (clasp == &ArrayObject::class_) { + MElements* elements = MElements::New(alloc(), obj); + current->add(elements); + + end = MArrayLength::New(alloc(), elements); + current->add(end->toInstruction()); + } else { + end = MUnboxedArrayLength::New(alloc(), obj); + current->add(end->toInstruction()); + } + + MArraySlice* ins = MArraySlice::New(alloc(), constraints(), + obj, begin, end, + templateObj, + templateObj->group()->initialHeap(constraints()), + unboxedType); + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + + if (!pushTypeBarrier(ins, getInlineReturnTypeSet(), BarrierKind::TypeSet)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathAbs(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType returnType = getInlineReturnType(); + MIRType argType = callInfo.getArg(0)->type(); + if (!IsNumberType(argType)) + return InliningStatus_NotInlined; + + // Either argType == returnType, or + // argType == Double or Float32, returnType == Int, or + // argType == Float32, returnType == Double + if (argType != returnType && !(IsFloatingPointType(argType) && returnType == MIRType::Int32) + && !(argType == MIRType::Float32 && returnType == MIRType::Double)) + { + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + // If the arg is a Float32, we specialize the op as double, it will be specialized + // as float32 if necessary later. + MIRType absType = (argType == MIRType::Float32) ? MIRType::Double : argType; + MInstruction* ins = MAbs::New(alloc(), callInfo.getArg(0), absType); + current->add(ins); + + current->push(ins); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathFloor(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType argType = callInfo.getArg(0)->type(); + MIRType returnType = getInlineReturnType(); + + // Math.floor(int(x)) == int(x) + if (argType == MIRType::Int32 && returnType == MIRType::Int32) { + callInfo.setImplicitlyUsedUnchecked(); + // The int operand may be something which bails out if the actual value + // is not in the range of the result type of the MIR. We need to tell + // the optimizer to preserve this bailout even if the final result is + // fully truncated. + MLimitedTruncate* ins = MLimitedTruncate::New(alloc(), callInfo.getArg(0), + MDefinition::IndirectTruncate); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + if (IsFloatingPointType(argType) && returnType == MIRType::Int32) { + callInfo.setImplicitlyUsedUnchecked(); + MFloor* ins = MFloor::New(alloc(), callInfo.getArg(0)); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + if (IsFloatingPointType(argType) && returnType == MIRType::Double) { + callInfo.setImplicitlyUsedUnchecked(); + MMathFunction* ins = MMathFunction::New(alloc(), callInfo.getArg(0), MMathFunction::Floor, nullptr); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + return InliningStatus_NotInlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathCeil(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType argType = callInfo.getArg(0)->type(); + MIRType returnType = getInlineReturnType(); + + // Math.ceil(int(x)) == int(x) + if (argType == MIRType::Int32 && returnType == MIRType::Int32) { + callInfo.setImplicitlyUsedUnchecked(); + // The int operand may be something which bails out if the actual value + // is not in the range of the result type of the MIR. We need to tell + // the optimizer to preserve this bailout even if the final result is + // fully truncated. + MLimitedTruncate* ins = MLimitedTruncate::New(alloc(), callInfo.getArg(0), + MDefinition::IndirectTruncate); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + if (IsFloatingPointType(argType) && returnType == MIRType::Int32) { + callInfo.setImplicitlyUsedUnchecked(); + MCeil* ins = MCeil::New(alloc(), callInfo.getArg(0)); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + if (IsFloatingPointType(argType) && returnType == MIRType::Double) { + callInfo.setImplicitlyUsedUnchecked(); + MMathFunction* ins = MMathFunction::New(alloc(), callInfo.getArg(0), MMathFunction::Ceil, nullptr); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + return InliningStatus_NotInlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathClz32(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType returnType = getInlineReturnType(); + if (returnType != MIRType::Int32) + return InliningStatus_NotInlined; + + if (!IsNumberType(callInfo.getArg(0)->type())) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MClz* ins = MClz::New(alloc(), callInfo.getArg(0), MIRType::Int32); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathRound(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType returnType = getInlineReturnType(); + MIRType argType = callInfo.getArg(0)->type(); + + // Math.round(int(x)) == int(x) + if (argType == MIRType::Int32 && returnType == MIRType::Int32) { + callInfo.setImplicitlyUsedUnchecked(); + // The int operand may be something which bails out if the actual value + // is not in the range of the result type of the MIR. We need to tell + // the optimizer to preserve this bailout even if the final result is + // fully truncated. + MLimitedTruncate* ins = MLimitedTruncate::New(alloc(), callInfo.getArg(0), + MDefinition::IndirectTruncate); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + if (IsFloatingPointType(argType) && returnType == MIRType::Int32) { + callInfo.setImplicitlyUsedUnchecked(); + MRound* ins = MRound::New(alloc(), callInfo.getArg(0)); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + if (IsFloatingPointType(argType) && returnType == MIRType::Double) { + callInfo.setImplicitlyUsedUnchecked(); + MMathFunction* ins = MMathFunction::New(alloc(), callInfo.getArg(0), MMathFunction::Round, nullptr); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + return InliningStatus_NotInlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathSqrt(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType argType = callInfo.getArg(0)->type(); + if (getInlineReturnType() != MIRType::Double) + return InliningStatus_NotInlined; + if (!IsNumberType(argType)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MSqrt* sqrt = MSqrt::New(alloc(), callInfo.getArg(0), MIRType::Double); + current->add(sqrt); + current->push(sqrt); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathAtan2(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Double) + return InliningStatus_NotInlined; + + MIRType argType0 = callInfo.getArg(0)->type(); + MIRType argType1 = callInfo.getArg(1)->type(); + + if (!IsNumberType(argType0) || !IsNumberType(argType1)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MAtan2* atan2 = MAtan2::New(alloc(), callInfo.getArg(0), callInfo.getArg(1)); + current->add(atan2); + current->push(atan2); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathHypot(CallInfo& callInfo) +{ + if (callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + uint32_t argc = callInfo.argc(); + if (argc < 2 || argc > 4) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Double) + return InliningStatus_NotInlined; + + MDefinitionVector vector(alloc()); + if (!vector.reserve(argc)) + return InliningStatus_NotInlined; + + for (uint32_t i = 0; i < argc; ++i) { + MDefinition * arg = callInfo.getArg(i); + if (!IsNumberType(arg->type())) + return InliningStatus_NotInlined; + vector.infallibleAppend(arg); + } + + callInfo.setImplicitlyUsedUnchecked(); + MHypot* hypot = MHypot::New(alloc(), vector); + + if (!hypot) + return InliningStatus_NotInlined; + + current->add(hypot); + current->push(hypot); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathPow(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + bool emitted = false; + if (!powTrySpecialized(&emitted, callInfo.getArg(0), callInfo.getArg(1), + getInlineReturnType())) + { + return InliningStatus_Error; + } + + if (!emitted) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathRandom(CallInfo& callInfo) +{ + if (callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Double) + return InliningStatus_NotInlined; + + // MRandom JIT code directly accesses the RNG. It's (barely) possible to + // inline Math.random without it having been called yet, so ensure RNG + // state that isn't guaranteed to be initialized already. + script()->compartment()->ensureRandomNumberGenerator(); + + callInfo.setImplicitlyUsedUnchecked(); + + MRandom* rand = MRandom::New(alloc()); + current->add(rand); + current->push(rand); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathImul(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType returnType = getInlineReturnType(); + if (returnType != MIRType::Int32) + return InliningStatus_NotInlined; + + if (!IsNumberType(callInfo.getArg(0)->type())) + return InliningStatus_NotInlined; + if (!IsNumberType(callInfo.getArg(1)->type())) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* first = MTruncateToInt32::New(alloc(), callInfo.getArg(0)); + current->add(first); + + MInstruction* second = MTruncateToInt32::New(alloc(), callInfo.getArg(1)); + current->add(second); + + MMul* ins = MMul::New(alloc(), first, second, MIRType::Int32, MMul::Integer); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathFRound(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // MIRType can't be Float32, as this point, as getInlineReturnType uses JSVal types + // to infer the returned MIR type. + TemporaryTypeSet* returned = getInlineReturnTypeSet(); + if (returned->empty()) { + // As there's only one possible returned type, just add it to the observed + // returned typeset + returned->addType(TypeSet::DoubleType(), alloc_->lifoAlloc()); + } else { + MIRType returnType = getInlineReturnType(); + if (!IsNumberType(returnType)) + return InliningStatus_NotInlined; + } + + MIRType arg = callInfo.getArg(0)->type(); + if (!IsNumberType(arg)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MToFloat32* ins = MToFloat32::New(alloc(), callInfo.getArg(0)); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineMathMinMax(CallInfo& callInfo, bool max) +{ + if (callInfo.argc() < 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MIRType returnType = getInlineReturnType(); + if (!IsNumberType(returnType)) + return InliningStatus_NotInlined; + + MDefinitionVector int32_cases(alloc()); + for (unsigned i = 0; i < callInfo.argc(); i++) { + MDefinition* arg = callInfo.getArg(i); + + switch (arg->type()) { + case MIRType::Int32: + if (!int32_cases.append(arg)) + return InliningStatus_Error; + break; + case MIRType::Double: + case MIRType::Float32: + // Don't force a double MMinMax for arguments that would be a NOP + // when doing an integer MMinMax. + if (arg->isConstant()) { + double cte = arg->toConstant()->numberToDouble(); + // min(int32, cte >= INT32_MAX) = int32 + if (cte >= INT32_MAX && !max) + break; + // max(int32, cte <= INT32_MIN) = int32 + if (cte <= INT32_MIN && max) + break; + } + + // Force double MMinMax if argument is a "effectfull" double. + returnType = MIRType::Double; + break; + default: + return InliningStatus_NotInlined; + } + } + + if (int32_cases.length() == 0) + returnType = MIRType::Double; + + callInfo.setImplicitlyUsedUnchecked(); + + MDefinitionVector& cases = (returnType == MIRType::Int32) ? int32_cases : callInfo.argv(); + + if (cases.length() == 1) { + MLimitedTruncate* limit = MLimitedTruncate::New(alloc(), cases[0], MDefinition::NoTruncate); + current->add(limit); + current->push(limit); + return InliningStatus_Inlined; + } + + // Chain N-1 MMinMax instructions to compute the MinMax. + MMinMax* last = MMinMax::New(alloc(), cases[0], cases[1], returnType, max); + current->add(last); + + for (unsigned i = 2; i < cases.length(); i++) { + MMinMax* ins = MMinMax::New(alloc(), last, cases[i], returnType, max); + current->add(ins); + last = ins; + } + + current->push(last); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineStringObject(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || !callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // ConvertToString doesn't support objects. + if (callInfo.getArg(0)->mightBeType(MIRType::Object)) + return InliningStatus_NotInlined; + + JSObject* templateObj = inspector->getTemplateObjectForNative(pc, StringConstructor); + if (!templateObj) + return InliningStatus_NotInlined; + MOZ_ASSERT(templateObj->is<StringObject>()); + + callInfo.setImplicitlyUsedUnchecked(); + + MNewStringObject* ins = MNewStringObject::New(alloc(), callInfo.getArg(0), templateObj); + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo) +{ + if (!callInfo.getArg(0)->isConstant()) + return InliningStatus_NotInlined; + + if (!callInfo.getArg(1)->isConstant()) + return InliningStatus_NotInlined; + + MConstant* strval = callInfo.getArg(0)->toConstant(); + if (strval->type() != MIRType::String) + return InliningStatus_NotInlined; + + MConstant* sepval = callInfo.getArg(1)->toConstant(); + if (strval->type() != MIRType::String) + return InliningStatus_NotInlined; + + // Check if exist a template object in stub. + JSString* stringStr = nullptr; + JSString* stringSep = nullptr; + JSObject* templateObject = nullptr; + if (!inspector->isOptimizableCallStringSplit(pc, &stringStr, &stringSep, &templateObject)) + return InliningStatus_NotInlined; + + MOZ_ASSERT(stringStr); + MOZ_ASSERT(stringSep); + MOZ_ASSERT(templateObject); + + if (strval->toString() != stringStr) + return InliningStatus_NotInlined; + + if (sepval->toString() != stringSep) + return InliningStatus_NotInlined; + + // Check if |templateObject| is valid. + TypeSet::ObjectKey* retType = TypeSet::ObjectKey::get(templateObject); + if (retType->unknownProperties()) + return InliningStatus_NotInlined; + + HeapTypeSetKey key = retType->property(JSID_VOID); + if (!key.maybeTypes()) + return InliningStatus_NotInlined; + + if (!key.maybeTypes()->hasType(TypeSet::StringType())) + return InliningStatus_NotInlined; + + uint32_t initLength = GetAnyBoxedOrUnboxedArrayLength(templateObject); + if (GetAnyBoxedOrUnboxedInitializedLength(templateObject) != initLength) + return InliningStatus_NotInlined; + + Vector<MConstant*, 0, SystemAllocPolicy> arrayValues; + for (uint32_t i = 0; i < initLength; i++) { + Value str = GetAnyBoxedOrUnboxedDenseElement(templateObject, i); + MOZ_ASSERT(str.toString()->isAtom()); + MConstant* value = MConstant::New(alloc().fallible(), str, constraints()); + if (!value) + return InliningStatus_Error; + if (!TypeSetIncludes(key.maybeTypes(), value->type(), value->resultTypeSet())) + return InliningStatus_NotInlined; + + if (!arrayValues.append(value)) + return InliningStatus_Error; + } + callInfo.setImplicitlyUsedUnchecked(); + + TemporaryTypeSet::DoubleConversion conversion = + getInlineReturnTypeSet()->convertDoubleElements(constraints()); + if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles) + return InliningStatus_NotInlined; + + if (!jsop_newarray(templateObject, initLength)) + return InliningStatus_Error; + + MDefinition* array = current->peek(-1); + + if (!initLength) { + if (!array->isResumePoint()) { + if (!resumeAfter(array->toNewArray())) + return InliningStatus_Error; + } + return InliningStatus_Inlined; + } + + JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject); + + // Store all values, no need to initialize the length after each as + // jsop_initelem_array is doing because we do not expect to bailout + // because the memory is supposed to be allocated by now. + for (uint32_t i = 0; i < initLength; i++) { + if (!alloc().ensureBallast()) + return InliningStatus_Error; + + MConstant* value = arrayValues[i]; + current->add(value); + + if (!initializeArrayElement(array, i, value, unboxedType, /* addResumePoint = */ false)) + return InliningStatus_Error; + } + + MInstruction* setLength = setInitializedLength(array, unboxedType, initLength); + if (!resumeAfter(setLength)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineStringSplitString(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* strArg = callInfo.getArg(0); + MDefinition* sepArg = callInfo.getArg(1); + + if (strArg->type() != MIRType::String) + return InliningStatus_NotInlined; + + if (sepArg->type() != MIRType::String) + return InliningStatus_NotInlined; + + IonBuilder::InliningStatus resultConstStringSplit = inlineConstantStringSplitString(callInfo); + if (resultConstStringSplit != InliningStatus_NotInlined) + return resultConstStringSplit; + + JSObject* templateObject = inspector->getTemplateObjectForNative(pc, js::intrinsic_StringSplitString); + if (!templateObject) + return InliningStatus_NotInlined; + + TypeSet::ObjectKey* retKey = TypeSet::ObjectKey::get(templateObject); + if (retKey->unknownProperties()) + return InliningStatus_NotInlined; + + HeapTypeSetKey key = retKey->property(JSID_VOID); + if (!key.maybeTypes()) + return InliningStatus_NotInlined; + + if (!key.maybeTypes()->hasType(TypeSet::StringType())) { + key.freeze(constraints()); + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + MConstant* templateObjectDef = MConstant::New(alloc(), ObjectValue(*templateObject), + constraints()); + current->add(templateObjectDef); + + MStringSplit* ins = MStringSplit::New(alloc(), constraints(), strArg, sepArg, + templateObjectDef); + current->add(ins); + current->push(ins); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineObjectHasPrototype(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* objArg = callInfo.getArg(0); + MDefinition* protoArg = callInfo.getArg(1); + + if (objArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (protoArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + // Inline only when both obj and proto are singleton objects and + // obj does not have uncacheable proto and obj.__proto__ is proto. + TemporaryTypeSet* objTypes = objArg->resultTypeSet(); + if (!objTypes || objTypes->unknownObject() || objTypes->getObjectCount() != 1) + return InliningStatus_NotInlined; + + TypeSet::ObjectKey* objKey = objTypes->getObject(0); + if (!objKey || !objKey->hasStableClassAndProto(constraints())) + return InliningStatus_NotInlined; + if (!objKey->isSingleton() || !objKey->singleton()->is<NativeObject>()) + return InliningStatus_NotInlined; + + JSObject* obj = &objKey->singleton()->as<NativeObject>(); + if (obj->hasUncacheableProto()) + return InliningStatus_NotInlined; + + JSObject* actualProto = checkNurseryObject(objKey->proto().toObjectOrNull()); + if (actualProto == nullptr) + return InliningStatus_NotInlined; + + TemporaryTypeSet* protoTypes = protoArg->resultTypeSet(); + if (!protoTypes || protoTypes->unknownObject() || protoTypes->getObjectCount() != 1) + return InliningStatus_NotInlined; + + TypeSet::ObjectKey* protoKey = protoTypes->getObject(0); + if (!protoKey || !protoKey->hasStableClassAndProto(constraints())) + return InliningStatus_NotInlined; + if (!protoKey->isSingleton() || !protoKey->singleton()->is<NativeObject>()) + return InliningStatus_NotInlined; + + JSObject* proto = &protoKey->singleton()->as<NativeObject>(); + pushConstant(BooleanValue(proto == actualProto)); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineStrCharCodeAt(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Int32) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType::String && callInfo.thisArg()->type() != MIRType::Value) + return InliningStatus_NotInlined; + MIRType argType = callInfo.getArg(0)->type(); + if (argType != MIRType::Int32 && argType != MIRType::Double) + return InliningStatus_NotInlined; + + // Check for STR.charCodeAt(IDX) where STR is a constant string and IDX is a + // constant integer. + InliningStatus constInlineStatus = inlineConstantCharCodeAt(callInfo); + if (constInlineStatus != InliningStatus_NotInlined) + return constInlineStatus; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* index = MToInt32::New(alloc(), callInfo.getArg(0)); + current->add(index); + + MStringLength* length = MStringLength::New(alloc(), callInfo.thisArg()); + current->add(length); + + index = addBoundsCheck(index, length); + + MCharCodeAt* charCode = MCharCodeAt::New(alloc(), callInfo.thisArg(), index); + current->add(charCode); + current->push(charCode); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineConstantCharCodeAt(CallInfo& callInfo) +{ + if (!callInfo.thisArg()->maybeConstantValue() || !callInfo.getArg(0)->maybeConstantValue()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); + return InliningStatus_NotInlined; + } + + MConstant* strval = callInfo.thisArg()->maybeConstantValue(); + MConstant* idxval = callInfo.getArg(0)->maybeConstantValue(); + + if (strval->type() != MIRType::String || idxval->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + JSString* str = strval->toString(); + if (!str->isLinear()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); + return InliningStatus_NotInlined; + } + + int32_t idx = idxval->toInt32(); + if (idx < 0 || (uint32_t(idx) >= str->length())) { + trackOptimizationOutcome(TrackedOutcome::OutOfBounds); + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + JSLinearString& linstr = str->asLinear(); + char16_t ch = linstr.latin1OrTwoByteChar(idx); + MConstant* result = MConstant::New(alloc(), Int32Value(ch)); + current->add(result); + current->push(result); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineStrFromCharCode(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::String) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MFromCharCode* string = MFromCharCode::New(alloc(), callInfo.getArg(0)); + current->add(string); + current->push(string); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineStrFromCodePoint(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::String) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MFromCodePoint* string = MFromCodePoint::New(alloc(), callInfo.getArg(0)); + current->add(string); + current->push(string); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineStrCharAt(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::String) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType::String) + return InliningStatus_NotInlined; + MIRType argType = callInfo.getArg(0)->type(); + if (argType != MIRType::Int32 && argType != MIRType::Double) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* index = MToInt32::New(alloc(), callInfo.getArg(0)); + current->add(index); + + MStringLength* length = MStringLength::New(alloc(), callInfo.thisArg()); + current->add(length); + + index = addBoundsCheck(index, length); + + // String.charAt(x) = String.fromCharCode(String.charCodeAt(x)) + MCharCodeAt* charCode = MCharCodeAt::New(alloc(), callInfo.thisArg(), index); + current->add(charCode); + + MFromCharCode* string = MFromCharCode::New(alloc(), charCode); + current->add(string); + current->push(string); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineRegExpMatcher(CallInfo& callInfo) +{ + // This is called from Self-hosted JS, after testing each argument, + // most of following tests should be passed. + + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* rxArg = callInfo.getArg(0); + MDefinition* strArg = callInfo.getArg(1); + MDefinition* lastIndexArg = callInfo.getArg(2); + + if (rxArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* rxTypes = rxArg->resultTypeSet(); + const Class* clasp = rxTypes ? rxTypes->getKnownClass(constraints()) : nullptr; + if (clasp != &RegExpObject::class_) + return InliningStatus_NotInlined; + + if (strArg->mightBeType(MIRType::Object)) + return InliningStatus_NotInlined; + + if (lastIndexArg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + JSContext* cx = GetJitContext()->cx; + if (!cx->compartment()->jitCompartment()->ensureRegExpMatcherStubExists(cx)) { + cx->clearPendingException(); // OOM or overrecursion. + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* matcher = MRegExpMatcher::New(alloc(), rxArg, strArg, lastIndexArg); + current->add(matcher); + current->push(matcher); + + if (!resumeAfter(matcher)) + return InliningStatus_Error; + + if (!pushTypeBarrier(matcher, getInlineReturnTypeSet(), BarrierKind::TypeSet)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineRegExpSearcher(CallInfo& callInfo) +{ + // This is called from Self-hosted JS, after testing each argument, + // most of following tests should be passed. + + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* rxArg = callInfo.getArg(0); + MDefinition* strArg = callInfo.getArg(1); + MDefinition* lastIndexArg = callInfo.getArg(2); + + if (rxArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* regexpTypes = rxArg->resultTypeSet(); + const Class* clasp = regexpTypes ? regexpTypes->getKnownClass(constraints()) : nullptr; + if (clasp != &RegExpObject::class_) + return InliningStatus_NotInlined; + + if (strArg->mightBeType(MIRType::Object)) + return InliningStatus_NotInlined; + + if (lastIndexArg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + JSContext* cx = GetJitContext()->cx; + if (!cx->compartment()->jitCompartment()->ensureRegExpSearcherStubExists(cx)) { + cx->clearPendingException(); // OOM or overrecursion. + return InliningStatus_Error; + } + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* searcher = MRegExpSearcher::New(alloc(), rxArg, strArg, lastIndexArg); + current->add(searcher); + current->push(searcher); + + if (!resumeAfter(searcher)) + return InliningStatus_Error; + + if (!pushTypeBarrier(searcher, getInlineReturnTypeSet(), BarrierKind::TypeSet)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineRegExpTester(CallInfo& callInfo) +{ + // This is called from Self-hosted JS, after testing each argument, + // most of following tests should be passed. + + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* rxArg = callInfo.getArg(0); + MDefinition* strArg = callInfo.getArg(1); + MDefinition* lastIndexArg = callInfo.getArg(2); + + if (rxArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* rxTypes = rxArg->resultTypeSet(); + const Class* clasp = rxTypes ? rxTypes->getKnownClass(constraints()) : nullptr; + if (clasp != &RegExpObject::class_) + return InliningStatus_NotInlined; + + if (strArg->mightBeType(MIRType::Object)) + return InliningStatus_NotInlined; + + if (lastIndexArg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + JSContext* cx = GetJitContext()->cx; + if (!cx->compartment()->jitCompartment()->ensureRegExpTesterStubExists(cx)) { + cx->clearPendingException(); // OOM or overrecursion. + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* tester = MRegExpTester::New(alloc(), rxArg, strArg, lastIndexArg); + current->add(tester); + current->push(tester); + + if (!resumeAfter(tester)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsRegExpObject(CallInfo& callInfo) +{ + if (callInfo.constructing() || callInfo.argc() != 1) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + MDefinition* arg = callInfo.getArg(0); + + bool isRegExpObject; + if (!arg->mightBeType(MIRType::Object)) { + isRegExpObject = false; + } else { + if (arg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* types = arg->resultTypeSet(); + const Class* clasp = types ? types->getKnownClass(constraints()) : nullptr; + if (!clasp || clasp->isProxy()) + return InliningStatus_NotInlined; + + isRegExpObject = (clasp == &RegExpObject::class_); + } + + pushConstant(BooleanValue(isRegExpObject)); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineRegExpPrototypeOptimizable(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* protoArg = callInfo.getArg(0); + + if (protoArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* opt = MRegExpPrototypeOptimizable::New(alloc(), protoArg); + current->add(opt); + current->push(opt); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineRegExpInstanceOptimizable(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* rxArg = callInfo.getArg(0); + MDefinition* protoArg = callInfo.getArg(1); + + if (rxArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + if (protoArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* opt = MRegExpInstanceOptimizable::New(alloc(), rxArg, protoArg); + current->add(opt); + current->push(opt); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineGetFirstDollarIndex(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* strArg = callInfo.getArg(0); + + if (strArg->type() != MIRType::String) + return InliningStatus_NotInlined; + + if (getInlineReturnType() != MIRType::Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* ins = MGetFirstDollarIndex::New(alloc(), strArg); + current->add(ins); + current->push(ins); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineStringReplaceString(CallInfo& callInfo) +{ + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::String) + return InliningStatus_NotInlined; + + MDefinition* strArg = callInfo.getArg(0); + MDefinition* patArg = callInfo.getArg(1); + MDefinition* replArg = callInfo.getArg(2); + + if (strArg->type() != MIRType::String) + return InliningStatus_NotInlined; + + if (patArg->type() != MIRType::String) + return InliningStatus_NotInlined; + + if (replArg->type() != MIRType::String) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* cte = MStringReplace::New(alloc(), strArg, patArg, replArg); + current->add(cte); + current->push(cte); + if (cte->isEffectful() && !resumeAfter(cte)) + return InliningStatus_Error; + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineSubstringKernel(CallInfo& callInfo) +{ + MOZ_ASSERT(callInfo.argc() == 3); + MOZ_ASSERT(!callInfo.constructing()); + + // Return: String. + if (getInlineReturnType() != MIRType::String) + return InliningStatus_NotInlined; + + // Arg 0: String. + if (callInfo.getArg(0)->type() != MIRType::String) + return InliningStatus_NotInlined; + + // Arg 1: Int. + if (callInfo.getArg(1)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + // Arg 2: Int. + if (callInfo.getArg(2)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MSubstr* substr = MSubstr::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), + callInfo.getArg(2)); + current->add(substr); + current->push(substr); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineObjectCreate(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) + return InliningStatus_NotInlined; + + JSObject* templateObject = inspector->getTemplateObjectForNative(pc, obj_create); + if (!templateObject) + return InliningStatus_NotInlined; + + MOZ_ASSERT(templateObject->is<PlainObject>()); + MOZ_ASSERT(!templateObject->isSingleton()); + + // Ensure the argument matches the template object's prototype. + MDefinition* arg = callInfo.getArg(0); + if (JSObject* proto = templateObject->staticPrototype()) { + if (IsInsideNursery(proto)) + return InliningStatus_NotInlined; + + TemporaryTypeSet* types = arg->resultTypeSet(); + if (!types || types->maybeSingleton() != proto) + return InliningStatus_NotInlined; + + MOZ_ASSERT(types->getKnownMIRType() == MIRType::Object); + } else { + if (arg->type() != MIRType::Null) + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + bool emitted = false; + if (!newObjectTryTemplateObject(&emitted, templateObject)) + return InliningStatus_Error; + + MOZ_ASSERT(emitted); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineDefineDataProperty(CallInfo& callInfo) +{ + MOZ_ASSERT(!callInfo.constructing()); + + // Only handle definitions of plain data properties. + if (callInfo.argc() != 3) + return InliningStatus_NotInlined; + + MDefinition* obj = convertUnboxedObjects(callInfo.getArg(0)); + MDefinition* id = callInfo.getArg(1); + MDefinition* value = callInfo.getArg(2); + + if (ElementAccessHasExtraIndexedProperty(this, obj)) + return InliningStatus_NotInlined; + + // setElemTryDense will push the value as the result of the define instead + // of |undefined|, but this is fine if the rval is ignored (as it should be + // in self hosted code.) + MOZ_ASSERT(*GetNextPc(pc) == JSOP_POP); + + bool emitted = false; + if (!setElemTryDense(&emitted, obj, id, value, /* writeHole = */ true)) + return InliningStatus_Error; + if (!emitted) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineHasClass(CallInfo& callInfo, + const Class* clasp1, const Class* clasp2, + const Class* clasp3, const Class* clasp4) +{ + if (callInfo.constructing() || callInfo.argc() != 1) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + TemporaryTypeSet* types = callInfo.getArg(0)->resultTypeSet(); + const Class* knownClass = types ? types->getKnownClass(constraints()) : nullptr; + if (knownClass) { + pushConstant(BooleanValue(knownClass == clasp1 || + knownClass == clasp2 || + knownClass == clasp3 || + knownClass == clasp4)); + } else { + MHasClass* hasClass1 = MHasClass::New(alloc(), callInfo.getArg(0), clasp1); + current->add(hasClass1); + + if (!clasp2 && !clasp3 && !clasp4) { + current->push(hasClass1); + } else { + const Class* remaining[] = { clasp2, clasp3, clasp4 }; + MDefinition* last = hasClass1; + for (size_t i = 0; i < ArrayLength(remaining); i++) { + MHasClass* hasClass = MHasClass::New(alloc(), callInfo.getArg(0), remaining[i]); + current->add(hasClass); + MBitOr* either = MBitOr::New(alloc(), last, hasClass); + either->infer(inspector, pc); + current->add(either); + last = either; + } + + MDefinition* result = convertToBoolean(last); + current->push(result); + } + } + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineGetNextEntryForIterator(CallInfo& callInfo, MGetNextEntryForIterator::Mode mode) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* iterArg = callInfo.getArg(0); + MDefinition* resultArg = callInfo.getArg(1); + + if (iterArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* iterTypes = iterArg->resultTypeSet(); + const Class* iterClasp = iterTypes ? iterTypes->getKnownClass(constraints()) : nullptr; + if (mode == MGetNextEntryForIterator::Map) { + if (iterClasp != &MapIteratorObject::class_) + return InliningStatus_NotInlined; + } else { + MOZ_ASSERT(mode == MGetNextEntryForIterator::Set); + + if (iterClasp != &SetIteratorObject::class_) + return InliningStatus_NotInlined; + } + + if (resultArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* resultTypes = resultArg->resultTypeSet(); + const Class* resultClasp = resultTypes ? resultTypes->getKnownClass(constraints()) : nullptr; + if (resultClasp != &ArrayObject::class_) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* next = MGetNextEntryForIterator::New(alloc(), iterArg, resultArg, mode); + current->add(next); + current->push(next); + + if (!resumeAfter(next)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +static bool +IsArrayBufferObject(CompilerConstraintList* constraints, MDefinition* def) +{ + MOZ_ASSERT(def->type() == MIRType::Object); + + TemporaryTypeSet* types = def->resultTypeSet(); + if (!types) + return false; + + return types->getKnownClass(constraints) == &ArrayBufferObject::class_; +} + +IonBuilder::InliningStatus +IonBuilder::inlineArrayBufferByteLength(CallInfo& callInfo) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 1); + + MDefinition* objArg = callInfo.getArg(0); + if (objArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (getInlineReturnType() != MIRType::Int32) + return InliningStatus_NotInlined; + + MInstruction* ins = addArrayBufferByteLength(objArg); + current->push(ins); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlinePossiblyWrappedArrayBufferByteLength(CallInfo& callInfo) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 1); + + MDefinition* objArg = callInfo.getArg(0); + if (objArg->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (getInlineReturnType() != MIRType::Int32) + return InliningStatus_NotInlined; + + if (!IsArrayBufferObject(constraints(), objArg)) + return InliningStatus_NotInlined; + + MInstruction* ins = addArrayBufferByteLength(objArg); + current->push(ins); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineTypedArray(CallInfo& callInfo, Native native) +{ + if (!callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Object) + return InliningStatus_NotInlined; + if (callInfo.argc() != 1) + return InliningStatus_NotInlined; + + MDefinition* arg = callInfo.getArg(0); + + if (arg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + JSObject* templateObject = inspector->getTemplateObjectForNative(pc, native); + + if (!templateObject) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeNoTemplateObj); + return InliningStatus_NotInlined; + } + + MOZ_ASSERT(templateObject->is<TypedArrayObject>()); + TypedArrayObject* obj = &templateObject->as<TypedArrayObject>(); + + // Do not optimize when we see a template object with a singleton type, + // since it hits at most once. + if (templateObject->isSingleton()) + return InliningStatus_NotInlined; + + MInstruction* ins = nullptr; + + if (!arg->isConstant()) { + callInfo.setImplicitlyUsedUnchecked(); + ins = MNewTypedArrayDynamicLength::New(alloc(), constraints(), templateObject, + templateObject->group()->initialHeap(constraints()), + arg); + } else { + // Negative lengths must throw a RangeError. (We don't track that this + // might have previously thrown, when determining whether to inline, so we + // have to deal with this error case when inlining.) + int32_t providedLen = arg->maybeConstantValue()->toInt32(); + if (providedLen <= 0) + return InliningStatus_NotInlined; + + uint32_t len = AssertedCast<uint32_t>(providedLen); + + if (obj->length() != len) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), obj); + current->add(templateConst); + ins = MNewTypedArray::New(alloc(), constraints(), templateConst, + obj->group()->initialHeap(constraints())); + } + + current->add(ins); + current->push(ins); + if (!resumeAfter(ins)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsTypedArrayHelper(CallInfo& callInfo, WrappingBehavior wrappingBehavior) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 1); + + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + // The test is elaborate: in-line only if there is exact + // information. + + TemporaryTypeSet* types = callInfo.getArg(0)->resultTypeSet(); + if (!types) + return InliningStatus_NotInlined; + + bool result = false; + switch (types->forAllClasses(constraints(), IsTypedArrayClass)) { + case TemporaryTypeSet::ForAllResult::ALL_FALSE: + // Wrapped typed arrays won't appear to be typed arrays per a + // |forAllClasses| query. If wrapped typed arrays are to be considered + // typed arrays, a negative answer is not conclusive. Don't inline in + // that case. + if (wrappingBehavior == AllowWrappedTypedArrays) { + switch (types->forAllClasses(constraints(), IsProxyClass)) { + case TemporaryTypeSet::ForAllResult::ALL_FALSE: + case TemporaryTypeSet::ForAllResult::EMPTY: + break; + case TemporaryTypeSet::ForAllResult::ALL_TRUE: + case TemporaryTypeSet::ForAllResult::MIXED: + return InliningStatus_NotInlined; + } + } + + MOZ_FALLTHROUGH; + + case TemporaryTypeSet::ForAllResult::EMPTY: + result = false; + break; + + case TemporaryTypeSet::ForAllResult::ALL_TRUE: + result = true; + break; + + case TemporaryTypeSet::ForAllResult::MIXED: + return InliningStatus_NotInlined; + } + + pushConstant(BooleanValue(result)); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsTypedArray(CallInfo& callInfo) +{ + return inlineIsTypedArrayHelper(callInfo, RejectWrappedTypedArrays); +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsPossiblyWrappedTypedArray(CallInfo& callInfo) +{ + return inlineIsTypedArrayHelper(callInfo, AllowWrappedTypedArrays); +} + +static bool +IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def) +{ + MOZ_ASSERT(def->type() == MIRType::Object); + + TemporaryTypeSet* types = def->resultTypeSet(); + if (!types) + return false; + + return types->forAllClasses(constraints, IsTypedArrayClass) == + TemporaryTypeSet::ForAllResult::ALL_TRUE; +} + +IonBuilder::InliningStatus +IonBuilder::inlinePossiblyWrappedTypedArrayLength(CallInfo& callInfo) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 1); + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (getInlineReturnType() != MIRType::Int32) + return InliningStatus_NotInlined; + + if (!IsTypedArrayObject(constraints(), callInfo.getArg(0))) + return InliningStatus_NotInlined; + + MInstruction* length = addTypedArrayLength(callInfo.getArg(0)); + current->push(length); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineTypedArrayLength(CallInfo& callInfo) +{ + return inlinePossiblyWrappedTypedArrayLength(callInfo); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSetDisjointTypedElements(CallInfo& callInfo) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 3); + + // Initial argument requirements. + + MDefinition* target = callInfo.getArg(0); + if (target->type() != MIRType::Object) + return InliningStatus_NotInlined; + + if (getInlineReturnType() != MIRType::Undefined) + return InliningStatus_NotInlined; + + MDefinition* targetOffset = callInfo.getArg(1); + MOZ_ASSERT(targetOffset->type() == MIRType::Int32); + + MDefinition* sourceTypedArray = callInfo.getArg(2); + if (sourceTypedArray->type() != MIRType::Object) + return InliningStatus_NotInlined; + + // Only attempt to optimize if |target| and |sourceTypedArray| are both + // definitely typed arrays. (The former always is. The latter is not, + // necessarily, because of wrappers.) + if (!IsTypedArrayObject(constraints(), target) || + !IsTypedArrayObject(constraints(), sourceTypedArray)) + { + return InliningStatus_NotInlined; + } + + auto sets = MSetDisjointTypedElements::New(alloc(), target, targetOffset, sourceTypedArray); + current->add(sets); + + pushConstant(UndefinedValue()); + + if (!resumeAfter(sets)) + return InliningStatus_Error; + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineObjectIsTypeDescr(CallInfo& callInfo) +{ + if (callInfo.constructing() || callInfo.argc() != 1) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + // The test is elaborate: in-line only if there is exact + // information. + + TemporaryTypeSet* types = callInfo.getArg(0)->resultTypeSet(); + if (!types) + return InliningStatus_NotInlined; + + bool result = false; + switch (types->forAllClasses(constraints(), IsTypeDescrClass)) { + case TemporaryTypeSet::ForAllResult::ALL_FALSE: + case TemporaryTypeSet::ForAllResult::EMPTY: + result = false; + break; + case TemporaryTypeSet::ForAllResult::ALL_TRUE: + result = true; + break; + case TemporaryTypeSet::ForAllResult::MIXED: + return InliningStatus_NotInlined; + } + + pushConstant(BooleanValue(result)); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineSetTypedObjectOffset(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* typedObj = callInfo.getArg(0); + MDefinition* offset = callInfo.getArg(1); + + // Return type should be undefined or something wacky is going on. + if (getInlineReturnType() != MIRType::Undefined) + return InliningStatus_NotInlined; + + // Check typedObj is a, well, typed object. Go ahead and use TI + // data. If this check should fail, that is almost certainly a bug + // in self-hosted code -- either because it's not being careful + // with TI or because of something else -- but we'll just let it + // fall through to the SetTypedObjectOffset intrinsic in such + // cases. + TemporaryTypeSet* types = typedObj->resultTypeSet(); + if (typedObj->type() != MIRType::Object || !types) + return InliningStatus_NotInlined; + switch (types->forAllClasses(constraints(), IsTypedObjectClass)) { + case TemporaryTypeSet::ForAllResult::ALL_FALSE: + case TemporaryTypeSet::ForAllResult::EMPTY: + case TemporaryTypeSet::ForAllResult::MIXED: + return InliningStatus_NotInlined; + case TemporaryTypeSet::ForAllResult::ALL_TRUE: + break; + } + + // Check type of offset argument is an integer. + if (offset->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + MInstruction* ins = MSetTypedObjectOffset::New(alloc(), typedObj, offset); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineUnsafeSetReservedSlot(CallInfo& callInfo) +{ + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + if (getInlineReturnType() != MIRType::Undefined) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (callInfo.getArg(1)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + // Don't inline if we don't have a constant slot. + MDefinition* arg = callInfo.getArg(1); + if (!arg->isConstant()) + return InliningStatus_NotInlined; + uint32_t slot = uint32_t(arg->toConstant()->toInt32()); + + callInfo.setImplicitlyUsedUnchecked(); + + MStoreFixedSlot* store = + MStoreFixedSlot::NewBarriered(alloc(), callInfo.getArg(0), slot, callInfo.getArg(2)); + current->add(store); + current->push(store); + + if (NeedsPostBarrier(callInfo.getArg(2))) + current->add(MPostWriteBarrier::New(alloc(), callInfo.getArg(0), callInfo.getArg(2))); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineUnsafeGetReservedSlot(CallInfo& callInfo, MIRType knownValueType) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + if (callInfo.getArg(1)->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + // Don't inline if we don't have a constant slot. + MDefinition* arg = callInfo.getArg(1); + if (!arg->isConstant()) + return InliningStatus_NotInlined; + uint32_t slot = uint32_t(arg->toConstant()->toInt32()); + + callInfo.setImplicitlyUsedUnchecked(); + + MLoadFixedSlot* load = MLoadFixedSlot::New(alloc(), callInfo.getArg(0), slot); + current->add(load); + current->push(load); + if (knownValueType != MIRType::Value) { + // We know what type we have in this slot. Assert that this is in fact + // what we've seen coming from this slot in the past, then tell the + // MLoadFixedSlot about its result type. That will make us do an + // infallible unbox as part of the slot load and then we'll barrier on + // the unbox result. That way the type barrier code won't end up doing + // MIRType checks and conditional unboxing. + MOZ_ASSERT_IF(!getInlineReturnTypeSet()->empty(), + getInlineReturnType() == knownValueType); + load->setResultType(knownValueType); + } + + // We don't track reserved slot types, so always emit a barrier. + if (!pushTypeBarrier(load, getInlineReturnTypeSet(), BarrierKind::TypeSet)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsCallable(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + MDefinition* arg = callInfo.getArg(0); + // Do not inline if the type of arg is neither primitive nor object. + if (arg->type() > MIRType::Object) + return InliningStatus_NotInlined; + + // Try inlining with constant true/false: only objects may be callable at + // all, and if we know the class check if it is callable. + bool isCallableKnown = false; + bool isCallableConstant; + if (arg->type() != MIRType::Object) { + // Primitive (including undefined and null). + isCallableKnown = true; + isCallableConstant = false; + } else { + TemporaryTypeSet* types = arg->resultTypeSet(); + const Class* clasp = types ? types->getKnownClass(constraints()) : nullptr; + if (clasp && !clasp->isProxy()) { + isCallableKnown = true; + isCallableConstant = clasp->nonProxyCallable(); + } + } + + callInfo.setImplicitlyUsedUnchecked(); + + if (isCallableKnown) { + MConstant* constant = MConstant::New(alloc(), BooleanValue(isCallableConstant)); + current->add(constant); + current->push(constant); + return InliningStatus_Inlined; + } + + MIsCallable* isCallable = MIsCallable::New(alloc(), arg); + current->add(isCallable); + current->push(isCallable); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsConstructor(CallInfo& callInfo) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 1); + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MIsConstructor* ins = MIsConstructor::New(alloc(), callInfo.getArg(0)); + current->add(ins); + current->push(ins); + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsObject(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + if (callInfo.getArg(0)->type() == MIRType::Object) { + pushConstant(BooleanValue(true)); + } else { + MIsObject* isObject = MIsObject::New(alloc(), callInfo.getArg(0)); + current->add(isObject); + current->push(isObject); + } + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineToObject(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // If we know the input type is an object, nop ToObject. + if (getInlineReturnType() != MIRType::Object) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType::Object) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + MDefinition* object = callInfo.getArg(0); + + current->push(object); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsWrappedArrayConstructor(CallInfo& callInfo) +{ + if (callInfo.constructing() || callInfo.argc() != 1) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + MDefinition* arg = callInfo.getArg(0); + if (arg->type() != MIRType::Object) + return InliningStatus_NotInlined; + + TemporaryTypeSet* types = arg->resultTypeSet(); + switch (types->forAllClasses(constraints(), IsProxyClass)) { + case TemporaryTypeSet::ForAllResult::ALL_FALSE: + break; + case TemporaryTypeSet::ForAllResult::EMPTY: + case TemporaryTypeSet::ForAllResult::ALL_TRUE: + case TemporaryTypeSet::ForAllResult::MIXED: + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + // Inline only if argument is absolutely *not* a Proxy. + pushConstant(BooleanValue(false)); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineToInteger(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* input = callInfo.getArg(0); + + // Only optimize cases where input contains only number, null or boolean + if (input->mightBeType(MIRType::Object) || + input->mightBeType(MIRType::String) || + input->mightBeType(MIRType::Symbol) || + input->mightBeType(MIRType::Undefined) || + input->mightBeMagicType()) + { + return InliningStatus_NotInlined; + } + + MOZ_ASSERT(input->type() == MIRType::Value || input->type() == MIRType::Null || + input->type() == MIRType::Boolean || IsNumberType(input->type())); + + // Only optimize cases where output is int32 + if (getInlineReturnType() != MIRType::Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MToInt32* toInt32 = MToInt32::New(alloc(), callInfo.getArg(0)); + current->add(toInt32); + current->push(toInt32); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineToString(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) + return InliningStatus_NotInlined; + + if (getInlineReturnType() != MIRType::String) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + MToString* toString = MToString::New(alloc(), callInfo.getArg(0)); + current->add(toString); + current->push(toString); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineBailout(CallInfo& callInfo) +{ + callInfo.setImplicitlyUsedUnchecked(); + + current->add(MBail::New(alloc())); + + MConstant* undefined = MConstant::New(alloc(), UndefinedValue()); + current->add(undefined); + current->push(undefined); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAssertFloat32(CallInfo& callInfo) +{ + if (callInfo.argc() != 2) + return InliningStatus_NotInlined; + + MDefinition* secondArg = callInfo.getArg(1); + + MOZ_ASSERT(secondArg->type() == MIRType::Boolean); + MOZ_ASSERT(secondArg->isConstant()); + + bool mustBeFloat32 = secondArg->toConstant()->toBoolean(); + current->add(MAssertFloat32::New(alloc(), callInfo.getArg(0), mustBeFloat32)); + + MConstant* undefined = MConstant::New(alloc(), UndefinedValue()); + current->add(undefined); + current->push(undefined); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAssertRecoveredOnBailout(CallInfo& callInfo) +{ + if (callInfo.argc() != 2) + return InliningStatus_NotInlined; + + // Don't assert for recovered instructions when recovering is disabled. + if (JitOptions.disableRecoverIns) + return InliningStatus_NotInlined; + + if (JitOptions.checkRangeAnalysis) { + // If we are checking the range of all instructions, then the guards + // inserted by Range Analysis prevent the use of recover + // instruction. Thus, we just disable these checks. + current->push(constant(UndefinedValue())); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; + } + + MDefinition* secondArg = callInfo.getArg(1); + + MOZ_ASSERT(secondArg->type() == MIRType::Boolean); + MOZ_ASSERT(secondArg->isConstant()); + + bool mustBeRecovered = secondArg->toConstant()->toBoolean(); + MAssertRecoveredOnBailout* assert = + MAssertRecoveredOnBailout::New(alloc(), callInfo.getArg(0), mustBeRecovered); + current->add(assert); + current->push(assert); + + // Create an instruction sequence which implies that the argument of the + // assertRecoveredOnBailout function would be encoded at least in one + // Snapshot. + MNop* nop = MNop::New(alloc()); + current->add(nop); + if (!resumeAfter(nop)) + return InliningStatus_Error; + current->add(MEncodeSnapshot::New(alloc())); + + current->pop(); + current->push(constant(UndefinedValue())); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAtomicsCompareExchange(CallInfo& callInfo) +{ + if (callInfo.argc() != 4 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // These guards are desirable here and in subsequent atomics to + // avoid bad bailouts with MTruncateToInt32, see https://bugzilla.mozilla.org/show_bug.cgi?id=1141986#c20. + MDefinition* oldval = callInfo.getArg(2); + if (oldval->mightBeType(MIRType::Object) || oldval->mightBeType(MIRType::Symbol)) + return InliningStatus_NotInlined; + + MDefinition* newval = callInfo.getArg(3); + if (newval->mightBeType(MIRType::Object) || newval->mightBeType(MIRType::Symbol)) + return InliningStatus_NotInlined; + + Scalar::Type arrayType; + bool requiresCheck = false; + if (!atomicsMeetsPreconditions(callInfo, &arrayType, &requiresCheck)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* elements; + MDefinition* index; + atomicsCheckBounds(callInfo, &elements, &index); + + if (requiresCheck) + addSharedTypedArrayGuard(callInfo.getArg(0)); + + MCompareExchangeTypedArrayElement* cas = + MCompareExchangeTypedArrayElement::New(alloc(), elements, index, arrayType, oldval, newval); + cas->setResultType(getInlineReturnType()); + current->add(cas); + current->push(cas); + + if (!resumeAfter(cas)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAtomicsExchange(CallInfo& callInfo) +{ + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* value = callInfo.getArg(2); + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + return InliningStatus_NotInlined; + + Scalar::Type arrayType; + bool requiresCheck = false; + if (!atomicsMeetsPreconditions(callInfo, &arrayType, &requiresCheck)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* elements; + MDefinition* index; + atomicsCheckBounds(callInfo, &elements, &index); + + if (requiresCheck) + addSharedTypedArrayGuard(callInfo.getArg(0)); + + MInstruction* exchange = + MAtomicExchangeTypedArrayElement::New(alloc(), elements, index, value, arrayType); + exchange->setResultType(getInlineReturnType()); + current->add(exchange); + current->push(exchange); + + if (!resumeAfter(exchange)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAtomicsLoad(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + Scalar::Type arrayType; + bool requiresCheck = false; + if (!atomicsMeetsPreconditions(callInfo, &arrayType, &requiresCheck)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* elements; + MDefinition* index; + atomicsCheckBounds(callInfo, &elements, &index); + + if (requiresCheck) + addSharedTypedArrayGuard(callInfo.getArg(0)); + + MLoadUnboxedScalar* load = + MLoadUnboxedScalar::New(alloc(), elements, index, arrayType, + DoesRequireMemoryBarrier); + load->setResultType(getInlineReturnType()); + current->add(load); + current->push(load); + + // Loads are considered effectful (they execute a memory barrier). + if (!resumeAfter(load)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAtomicsStore(CallInfo& callInfo) +{ + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // Atomics.store() is annoying because it returns the result of converting + // the value by ToInteger(), not the input value, nor the result of + // converting the value by ToInt32(). It is especially annoying because + // almost nobody uses the result value. + // + // As an expedient compromise, therefore, we inline only if the result is + // obviously unused or if the argument is already Int32 and thus requires no + // conversion. + + MDefinition* value = callInfo.getArg(2); + if (!BytecodeIsPopped(pc) && value->type() != MIRType::Int32) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadType); + return InliningStatus_NotInlined; + } + + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + return InliningStatus_NotInlined; + + Scalar::Type arrayType; + bool requiresCheck = false; + if (!atomicsMeetsPreconditions(callInfo, &arrayType, &requiresCheck, DontCheckAtomicResult)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MInstruction* elements; + MDefinition* index; + atomicsCheckBounds(callInfo, &elements, &index); + + if (requiresCheck) + addSharedTypedArrayGuard(callInfo.getArg(0)); + + MDefinition* toWrite = value; + if (toWrite->type() != MIRType::Int32) { + toWrite = MTruncateToInt32::New(alloc(), toWrite); + current->add(toWrite->toInstruction()); + } + MStoreUnboxedScalar* store = + MStoreUnboxedScalar::New(alloc(), elements, index, toWrite, arrayType, + MStoreUnboxedScalar::TruncateInput, DoesRequireMemoryBarrier); + current->add(store); + current->push(value); // Either Int32 or not used; in either case correct + + if (!resumeAfter(store)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAtomicsBinop(CallInfo& callInfo, InlinableNative target) +{ + if (callInfo.argc() != 3 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* value = callInfo.getArg(2); + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + return InliningStatus_NotInlined; + + Scalar::Type arrayType; + bool requiresCheck = false; + if (!atomicsMeetsPreconditions(callInfo, &arrayType, &requiresCheck)) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + if (requiresCheck) + addSharedTypedArrayGuard(callInfo.getArg(0)); + + MInstruction* elements; + MDefinition* index; + atomicsCheckBounds(callInfo, &elements, &index); + + AtomicOp k = AtomicFetchAddOp; + switch (target) { + case InlinableNative::AtomicsAdd: + k = AtomicFetchAddOp; + break; + case InlinableNative::AtomicsSub: + k = AtomicFetchSubOp; + break; + case InlinableNative::AtomicsAnd: + k = AtomicFetchAndOp; + break; + case InlinableNative::AtomicsOr: + k = AtomicFetchOrOp; + break; + case InlinableNative::AtomicsXor: + k = AtomicFetchXorOp; + break; + default: + MOZ_CRASH("Bad atomic operation"); + } + + MAtomicTypedArrayElementBinop* binop = + MAtomicTypedArrayElementBinop::New(alloc(), k, elements, index, arrayType, value); + binop->setResultType(getInlineReturnType()); + current->add(binop); + current->push(binop); + + if (!resumeAfter(binop)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineAtomicsIsLockFree(CallInfo& callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + MAtomicIsLockFree* ilf = + MAtomicIsLockFree::New(alloc(), callInfo.getArg(0)); + current->add(ilf); + current->push(ilf); + + return InliningStatus_Inlined; +} + +bool +IonBuilder::atomicsMeetsPreconditions(CallInfo& callInfo, Scalar::Type* arrayType, + bool* requiresTagCheck, AtomicCheckResult checkResult) +{ + if (!JitSupportsAtomics()) + return false; + + if (callInfo.getArg(0)->type() != MIRType::Object) + return false; + + if (callInfo.getArg(1)->type() != MIRType::Int32) + return false; + + // Ensure that the first argument is a TypedArray that maps shared + // memory. + // + // Then check both that the element type is something we can + // optimize and that the return type is suitable for that element + // type. + + TemporaryTypeSet* arg0Types = callInfo.getArg(0)->resultTypeSet(); + if (!arg0Types) + return false; + + TemporaryTypeSet::TypedArraySharedness sharedness; + *arrayType = arg0Types->getTypedArrayType(constraints(), &sharedness); + *requiresTagCheck = sharedness != TemporaryTypeSet::KnownShared; + switch (*arrayType) { + case Scalar::Int8: + case Scalar::Uint8: + case Scalar::Int16: + case Scalar::Uint16: + case Scalar::Int32: + return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Int32; + case Scalar::Uint32: + // Bug 1077305: it would be attractive to allow inlining even + // if the inline return type is Int32, which it will frequently + // be. + return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Double; + default: + // Excludes floating types and Uint8Clamped. + return false; + } +} + +void +IonBuilder::atomicsCheckBounds(CallInfo& callInfo, MInstruction** elements, MDefinition** index) +{ + // Perform bounds checking and extract the elements vector. + MDefinition* obj = callInfo.getArg(0); + MInstruction* length = nullptr; + *index = callInfo.getArg(1); + *elements = nullptr; + addTypedArrayLengthAndData(obj, DoBoundsCheck, index, &length, elements); +} + +IonBuilder::InliningStatus +IonBuilder::inlineIsConstructing(CallInfo& callInfo) +{ + MOZ_ASSERT(!callInfo.constructing()); + MOZ_ASSERT(callInfo.argc() == 0); + MOZ_ASSERT(script()->functionNonDelazifying(), + "isConstructing() should only be called in function scripts"); + + if (getInlineReturnType() != MIRType::Boolean) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + if (inliningDepth_ == 0) { + MInstruction* ins = MIsConstructing::New(alloc()); + current->add(ins); + current->push(ins); + return InliningStatus_Inlined; + } + + bool constructing = inlineCallInfo_->constructing(); + pushConstant(BooleanValue(constructing)); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineConstructTypedObject(CallInfo& callInfo, TypeDescr* descr) +{ + // Only inline default constructors for now. + if (callInfo.argc() != 0) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + if (size_t(descr->size()) > InlineTypedObject::MaximumSize) + return InliningStatus_NotInlined; + + JSObject* obj = inspector->getTemplateObjectForClassHook(pc, descr->getClass()); + if (!obj || !obj->is<InlineTypedObject>()) + return InliningStatus_NotInlined; + + InlineTypedObject* templateObject = &obj->as<InlineTypedObject>(); + if (&templateObject->typeDescr() != descr) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MNewTypedObject* ins = MNewTypedObject::New(alloc(), constraints(), templateObject, + templateObject->group()->initialHeap(constraints())); + current->add(ins); + current->push(ins); + + return InliningStatus_Inlined; +} + +// Main entry point for SIMD inlining. +// When the controlling simdType is an integer type, sign indicates whether the lanes should +// be treated as signed or unsigned integers. +IonBuilder::InliningStatus +IonBuilder::inlineSimd(CallInfo& callInfo, JSFunction* target, SimdType type) +{ + if (!JitSupportsSimd()) { + trackOptimizationOutcome(TrackedOutcome::NoSimdJitSupport); + return InliningStatus_NotInlined; + } + + JSNative native = target->native(); + const JSJitInfo* jitInfo = target->jitInfo(); + MOZ_ASSERT(jitInfo && jitInfo->type() == JSJitInfo::InlinableNative); + SimdOperation simdOp = SimdOperation(jitInfo->nativeOp); + + switch(simdOp) { + case SimdOperation::Constructor: + // SIMD constructor calls are handled via inlineNonFunctionCall(), so + // they won't show up here where target is required to be a JSFunction. + // See also inlineConstructSimdObject(). + MOZ_CRASH("SIMD constructor call not expected."); + case SimdOperation::Fn_check: + return inlineSimdCheck(callInfo, native, type); + case SimdOperation::Fn_splat: + return inlineSimdSplat(callInfo, native, type); + case SimdOperation::Fn_extractLane: + return inlineSimdExtractLane(callInfo, native, type); + case SimdOperation::Fn_replaceLane: + return inlineSimdReplaceLane(callInfo, native, type); + case SimdOperation::Fn_select: + return inlineSimdSelect(callInfo, native, type); + case SimdOperation::Fn_swizzle: + return inlineSimdShuffle(callInfo, native, type, 1); + case SimdOperation::Fn_shuffle: + return inlineSimdShuffle(callInfo, native, type, 2); + + // Unary arithmetic. + case SimdOperation::Fn_abs: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::abs, type); + case SimdOperation::Fn_neg: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::neg, type); + case SimdOperation::Fn_not: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::not_, type); + case SimdOperation::Fn_reciprocalApproximation: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::reciprocalApproximation, + type); + case SimdOperation::Fn_reciprocalSqrtApproximation: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::reciprocalSqrtApproximation, + type); + case SimdOperation::Fn_sqrt: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::sqrt, type); + + // Binary arithmetic. + case SimdOperation::Fn_add: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_add, type); + case SimdOperation::Fn_sub: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_sub, type); + case SimdOperation::Fn_mul: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_mul, type); + case SimdOperation::Fn_div: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_div, type); + case SimdOperation::Fn_max: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_max, type); + case SimdOperation::Fn_min: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_min, type); + case SimdOperation::Fn_maxNum: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_maxNum, type); + case SimdOperation::Fn_minNum: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_minNum, type); + + // Binary saturating. + case SimdOperation::Fn_addSaturate: + return inlineSimdBinarySaturating(callInfo, native, MSimdBinarySaturating::add, type); + case SimdOperation::Fn_subSaturate: + return inlineSimdBinarySaturating(callInfo, native, MSimdBinarySaturating::sub, type); + + // Binary bitwise. + case SimdOperation::Fn_and: + return inlineSimdBinaryBitwise(callInfo, native, MSimdBinaryBitwise::and_, type); + case SimdOperation::Fn_or: + return inlineSimdBinaryBitwise(callInfo, native, MSimdBinaryBitwise::or_, type); + case SimdOperation::Fn_xor: + return inlineSimdBinaryBitwise(callInfo, native, MSimdBinaryBitwise::xor_, type); + + // Shifts. + case SimdOperation::Fn_shiftLeftByScalar: + return inlineSimdShift(callInfo, native, MSimdShift::lsh, type); + case SimdOperation::Fn_shiftRightByScalar: + return inlineSimdShift(callInfo, native, MSimdShift::rshForSign(GetSimdSign(type)), type); + + // Boolean unary. + case SimdOperation::Fn_allTrue: + return inlineSimdAnyAllTrue(callInfo, /* IsAllTrue= */true, native, type); + case SimdOperation::Fn_anyTrue: + return inlineSimdAnyAllTrue(callInfo, /* IsAllTrue= */false, native, type); + + // Comparisons. + case SimdOperation::Fn_lessThan: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::lessThan, type); + case SimdOperation::Fn_lessThanOrEqual: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::lessThanOrEqual, type); + case SimdOperation::Fn_equal: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::equal, type); + case SimdOperation::Fn_notEqual: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::notEqual, type); + case SimdOperation::Fn_greaterThan: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::greaterThan, type); + case SimdOperation::Fn_greaterThanOrEqual: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::greaterThanOrEqual, type); + + // Int <-> Float conversions. + case SimdOperation::Fn_fromInt32x4: + return inlineSimdConvert(callInfo, native, false, SimdType::Int32x4, type); + case SimdOperation::Fn_fromUint32x4: + return inlineSimdConvert(callInfo, native, false, SimdType::Uint32x4, type); + case SimdOperation::Fn_fromFloat32x4: + return inlineSimdConvert(callInfo, native, false, SimdType::Float32x4, type); + + // Load/store. + case SimdOperation::Fn_load: + return inlineSimdLoad(callInfo, native, type, GetSimdLanes(type)); + case SimdOperation::Fn_load1: + return inlineSimdLoad(callInfo, native, type, 1); + case SimdOperation::Fn_load2: + return inlineSimdLoad(callInfo, native, type, 2); + case SimdOperation::Fn_load3: + return inlineSimdLoad(callInfo, native, type, 3); + case SimdOperation::Fn_store: + return inlineSimdStore(callInfo, native, type, GetSimdLanes(type)); + case SimdOperation::Fn_store1: + return inlineSimdStore(callInfo, native, type, 1); + case SimdOperation::Fn_store2: + return inlineSimdStore(callInfo, native, type, 2); + case SimdOperation::Fn_store3: + return inlineSimdStore(callInfo, native, type, 3); + + // Bitcasts. One for each type with a memory representation. + case SimdOperation::Fn_fromInt32x4Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Int32x4, type); + case SimdOperation::Fn_fromUint32x4Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Uint32x4, type); + case SimdOperation::Fn_fromInt16x8Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Int16x8, type); + case SimdOperation::Fn_fromUint16x8Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Uint16x8, type); + case SimdOperation::Fn_fromInt8x16Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Int8x16, type); + case SimdOperation::Fn_fromUint8x16Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Uint8x16, type); + case SimdOperation::Fn_fromFloat32x4Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Float32x4, type); + case SimdOperation::Fn_fromFloat64x2Bits: + return InliningStatus_NotInlined; + } + + MOZ_CRASH("Unexpected SIMD opcode"); +} + +// The representation of boolean SIMD vectors is the same as the corresponding +// integer SIMD vectors with -1 lanes meaning true and 0 lanes meaning false. +// +// Functions that set the value of a boolean vector lane work by applying +// ToBoolean on the input argument, so they accept any argument type, just like +// the MNot and MTest instructions. +// +// Convert any scalar value into an appropriate SIMD lane value: An Int32 value +// that is either 0 for false or -1 for true. +MDefinition* +IonBuilder::convertToBooleanSimdLane(MDefinition* scalar) +{ + MSub* result; + + if (scalar->type() == MIRType::Boolean) { + // The input scalar is already a boolean with the int32 values 0 / 1. + // Compute result = 0 - scalar. + result = MSub::New(alloc(), constant(Int32Value(0)), scalar); + } else { + // For any other type, let MNot handle the conversion to boolean. + // Compute result = !scalar - 1. + MNot* inv = MNot::New(alloc(), scalar); + current->add(inv); + result = MSub::New(alloc(), inv, constant(Int32Value(1))); + } + + result->setInt32Specialization(); + current->add(result); + return result; +} + +IonBuilder::InliningStatus +IonBuilder::inlineConstructSimdObject(CallInfo& callInfo, SimdTypeDescr* descr) +{ + if (!JitSupportsSimd()) { + trackOptimizationOutcome(TrackedOutcome::NoSimdJitSupport); + return InliningStatus_NotInlined; + } + + // Generic constructor of SIMD valuesX4. + MIRType simdType; + if (!MaybeSimdTypeToMIRType(descr->type(), &simdType)) { + trackOptimizationOutcome(TrackedOutcome::SimdTypeNotOptimized); + return InliningStatus_NotInlined; + } + + // Take the templateObject out of Baseline ICs, such that we can box + // SIMD value type in the same kind of objects. + MOZ_ASSERT(size_t(descr->size(descr->type())) < InlineTypedObject::MaximumSize); + MOZ_ASSERT(descr->getClass() == &SimdTypeDescr::class_, + "getTemplateObjectForSimdCtor needs an update"); + + JSObject* templateObject = inspector->getTemplateObjectForSimdCtor(pc, descr->type()); + if (!templateObject) + return InliningStatus_NotInlined; + + // The previous assertion ensures this will never fail if we were able to + // allocate a templateObject in Baseline. + InlineTypedObject* inlineTypedObject = &templateObject->as<InlineTypedObject>(); + MOZ_ASSERT(&inlineTypedObject->typeDescr() == descr); + + // When there are missing arguments, provide a default value + // containing the coercion of 'undefined' to the right type. + MConstant* defVal = nullptr; + MIRType laneType = SimdTypeToLaneType(simdType); + unsigned lanes = SimdTypeToLength(simdType); + if (lanes != 4 || callInfo.argc() < lanes) { + if (laneType == MIRType::Int32 || laneType == MIRType::Boolean) { + // The default lane for a boolean vector is |false|, but + // |MSimdSplat|, |MSimdValueX4|, and |MSimdInsertElement| all + // require an Int32 argument with the value 0 or 01 to initialize a + // boolean lane. See also convertToBooleanSimdLane() which is + // idempotent with a 0 argument after constant folding. + defVal = constant(Int32Value(0)); + } else if (laneType == MIRType::Double) { + defVal = constant(DoubleNaNValue()); + } else { + MOZ_ASSERT(laneType == MIRType::Float32); + defVal = MConstant::NewFloat32(alloc(), JS::GenericNaN()); + current->add(defVal); + } + } + + MInstruction *values = nullptr; + + // Use the MSimdValueX4 constructor for X4 vectors. + if (lanes == 4) { + MDefinition* lane[4]; + for (unsigned i = 0; i < 4; i++) + lane[i] = callInfo.getArgWithDefault(i, defVal); + + // Convert boolean lanes into Int32 0 / -1. + if (laneType == MIRType::Boolean) { + for (unsigned i = 0; i < 4; i++) + lane[i] = convertToBooleanSimdLane(lane[i]); + } + + values = MSimdValueX4::New(alloc(), simdType, lane[0], lane[1], lane[2], lane[3]); + current->add(values); + } else { + // For general constructor calls, start from splat(defVal), insert one + // lane at a time. + values = MSimdSplat::New(alloc(), defVal, simdType); + current->add(values); + + // Stop early if constructor doesn't have enough arguments. These lanes + // then get the default value. + if (callInfo.argc() < lanes) + lanes = callInfo.argc(); + + for (unsigned i = 0; i < lanes; i++) { + MDefinition* lane = callInfo.getArg(i); + if (laneType == MIRType::Boolean) + lane = convertToBooleanSimdLane(lane); + values = MSimdInsertElement::New(alloc(), values, lane, i); + current->add(values); + } + } + + MSimdBox* obj = MSimdBox::New(alloc(), constraints(), values, inlineTypedObject, descr->type(), + inlineTypedObject->group()->initialHeap(constraints())); + current->add(obj); + current->push(obj); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +bool +IonBuilder::canInlineSimd(CallInfo& callInfo, JSNative native, unsigned numArgs, + InlineTypedObject** templateObj) +{ + if (callInfo.argc() != numArgs) + return false; + + JSObject* templateObject = inspector->getTemplateObjectForNative(pc, native); + if (!templateObject) + return false; + + *templateObj = &templateObject->as<InlineTypedObject>(); + return true; +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdCheck(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + // Unboxing checks the SIMD object type and throws a TypeError if it doesn't + // match type. + MDefinition *arg = unboxSimd(callInfo.getArg(0), type); + + // Create an unbox/box pair, expecting the box to be optimized away if + // anyone use the return value from this check() call. This is what you want + // for code like this: + // + // function f(x) { + // x = Int32x4.check(x) + // for(...) { + // y = Int32x4.add(x, ...) + // } + // + // The unboxing of x happens as early as possible, and only once. + return boxSimd(callInfo, arg, templateObj); +} + +// Given a value or object, insert a dynamic check that this is a SIMD object of +// the required SimdType, and unbox it into the corresponding SIMD MIRType. +// +// This represents the standard type checking that all the SIMD operations +// perform on their arguments. +MDefinition* +IonBuilder::unboxSimd(MDefinition* ins, SimdType type) +{ + // Trivial optimization: If ins is a MSimdBox of the same SIMD type, there + // is no way the unboxing could fail, and we can skip it altogether. + // This is the same thing MSimdUnbox::foldsTo() does, but we can save the + // memory allocation here. + if (ins->isSimdBox()) { + MSimdBox* box = ins->toSimdBox(); + if (box->simdType() == type) { + MDefinition* value = box->input(); + MOZ_ASSERT(value->type() == SimdTypeToMIRType(type)); + return value; + } + } + + MSimdUnbox* unbox = MSimdUnbox::New(alloc(), ins, type); + current->add(unbox); + return unbox; +} + +IonBuilder::InliningStatus +IonBuilder::boxSimd(CallInfo& callInfo, MDefinition* ins, InlineTypedObject* templateObj) +{ + SimdType simdType = templateObj->typeDescr().as<SimdTypeDescr>().type(); + MSimdBox* obj = MSimdBox::New(alloc(), constraints(), ins, templateObj, simdType, + templateObj->group()->initialHeap(constraints())); + + // In some cases, ins has already been added to current. + if (!ins->block() && ins->isInstruction()) + current->add(ins->toInstruction()); + current->add(obj); + current->push(obj); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdBinaryArith(CallInfo& callInfo, JSNative native, + MSimdBinaryArith::Operation op, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + + auto* ins = MSimdBinaryArith::AddLegalized(alloc(), current, lhs, rhs, op); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdBinaryBitwise(CallInfo& callInfo, JSNative native, + MSimdBinaryBitwise::Operation op, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + + auto* ins = MSimdBinaryBitwise::New(alloc(), lhs, rhs, op); + return boxSimd(callInfo, ins, templateObj); +} + +// Inline a binary SIMD operation where both arguments are SIMD types. +IonBuilder::InliningStatus +IonBuilder::inlineSimdBinarySaturating(CallInfo& callInfo, JSNative native, + MSimdBinarySaturating::Operation op, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + + MSimdBinarySaturating* ins = + MSimdBinarySaturating::New(alloc(), lhs, rhs, op, GetSimdSign(type)); + return boxSimd(callInfo, ins, templateObj); +} + +// Inline a SIMD shiftByScalar operation. +IonBuilder::InliningStatus +IonBuilder::inlineSimdShift(CallInfo& callInfo, JSNative native, MSimdShift::Operation op, + SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* vec = unboxSimd(callInfo.getArg(0), type); + + MInstruction* ins = MSimdShift::AddLegalized(alloc(), current, vec, callInfo.getArg(1), op); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdComp(CallInfo& callInfo, JSNative native, MSimdBinaryComp::Operation op, + SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + MInstruction* ins = + MSimdBinaryComp::AddLegalized(alloc(), current, lhs, rhs, op, GetSimdSign(type)); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdUnary(CallInfo& callInfo, JSNative native, MSimdUnaryArith::Operation op, + SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* arg = unboxSimd(callInfo.getArg(0), type); + + MSimdUnaryArith* ins = MSimdUnaryArith::New(alloc(), arg, op); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdSplat(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + MIRType mirType = SimdTypeToMIRType(type); + MDefinition* arg = callInfo.getArg(0); + + // Convert to 0 / -1 before splatting a boolean lane. + if (SimdTypeToLaneType(mirType) == MIRType::Boolean) + arg = convertToBooleanSimdLane(arg); + + MSimdSplat* ins = MSimdSplat::New(alloc(), arg, mirType); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdExtractLane(CallInfo& callInfo, JSNative native, SimdType type) +{ + // extractLane() returns a scalar, so don't use canInlineSimd() which looks + // for a template object. + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // Lane index. + MDefinition* arg = callInfo.getArg(1); + if (!arg->isConstant() || arg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + unsigned lane = arg->toConstant()->toInt32(); + if (lane >= GetSimdLanes(type)) + return InliningStatus_NotInlined; + + // Original vector. + MDefinition* orig = unboxSimd(callInfo.getArg(0), type); + MIRType vecType = orig->type(); + MIRType laneType = SimdTypeToLaneType(vecType); + SimdSign sign = GetSimdSign(type); + + // An Uint32 lane can't be represented in MIRType::Int32. Get it as a double. + if (type == SimdType::Uint32x4) + laneType = MIRType::Double; + + MSimdExtractElement* ins = + MSimdExtractElement::New(alloc(), orig, laneType, lane, sign); + current->add(ins); + current->push(ins); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdReplaceLane(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 3, &templateObj)) + return InliningStatus_NotInlined; + + // Lane index. + MDefinition* arg = callInfo.getArg(1); + if (!arg->isConstant() || arg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + unsigned lane = arg->toConstant()->toInt32(); + if (lane >= GetSimdLanes(type)) + return InliningStatus_NotInlined; + + // Original vector. + MDefinition* orig = unboxSimd(callInfo.getArg(0), type); + MIRType vecType = orig->type(); + + // Convert to 0 / -1 before inserting a boolean lane. + MDefinition* value = callInfo.getArg(2); + if (SimdTypeToLaneType(vecType) == MIRType::Boolean) + value = convertToBooleanSimdLane(value); + + MSimdInsertElement* ins = MSimdInsertElement::New(alloc(), orig, value, lane); + return boxSimd(callInfo, ins, templateObj); +} + +// Inline a SIMD conversion or bitcast. When isCast==false, one of the types +// must be floating point and the other integer. In this case, sign indicates if +// the integer lanes should be treated as signed or unsigned integers. +IonBuilder::InliningStatus +IonBuilder::inlineSimdConvert(CallInfo& callInfo, JSNative native, bool isCast, SimdType fromType, + SimdType toType) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* arg = unboxSimd(callInfo.getArg(0), fromType); + MIRType mirType = SimdTypeToMIRType(toType); + + MInstruction* ins; + if (isCast) { + // Signed/Unsigned doesn't matter for bitcasts. + ins = MSimdReinterpretCast::New(alloc(), arg, mirType); + } else { + // Exactly one of fromType, toType must be an integer type. + SimdSign sign = GetSimdSign(fromType); + if (sign == SimdSign::NotApplicable) + sign = GetSimdSign(toType); + + // Possibly expand into multiple instructions. + ins = MSimdConvert::AddLegalized(alloc(), current, arg, mirType, sign); + } + + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdSelect(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 3, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* mask = unboxSimd(callInfo.getArg(0), GetBooleanSimdType(type)); + MDefinition* tval = unboxSimd(callInfo.getArg(1), type); + MDefinition* fval = unboxSimd(callInfo.getArg(2), type); + + MSimdSelect* ins = MSimdSelect::New(alloc(), mask, tval, fval); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdShuffle(CallInfo& callInfo, JSNative native, SimdType type, + unsigned numVectors) +{ + unsigned numLanes = GetSimdLanes(type); + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, numVectors + numLanes, &templateObj)) + return InliningStatus_NotInlined; + + MIRType mirType = SimdTypeToMIRType(type); + + MSimdGeneralShuffle* ins = MSimdGeneralShuffle::New(alloc(), numVectors, numLanes, mirType); + + if (!ins->init(alloc())) + return InliningStatus_Error; + + for (unsigned i = 0; i < numVectors; i++) + ins->setVector(i, unboxSimd(callInfo.getArg(i), type)); + for (size_t i = 0; i < numLanes; i++) + ins->setLane(i, callInfo.getArg(numVectors + i)); + + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdAnyAllTrue(CallInfo& callInfo, bool IsAllTrue, JSNative native, + SimdType type) +{ + // anyTrue() / allTrue() return a scalar, so don't use canInlineSimd() which looks + // for a template object. + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* arg = unboxSimd(callInfo.getArg(0), type); + + MUnaryInstruction* ins; + if (IsAllTrue) + ins = MSimdAllTrue::New(alloc(), arg, MIRType::Boolean); + else + ins = MSimdAnyTrue::New(alloc(), arg, MIRType::Boolean); + + current->add(ins); + current->push(ins); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +// Get the typed array element type corresponding to the lanes in a SIMD vector type. +// This only applies to SIMD types that can be loaded and stored to a typed array. +static Scalar::Type +SimdTypeToArrayElementType(SimdType type) +{ + switch (type) { + case SimdType::Float32x4: return Scalar::Float32x4; + case SimdType::Int8x16: + case SimdType::Uint8x16: return Scalar::Int8x16; + case SimdType::Int16x8: + case SimdType::Uint16x8: return Scalar::Int16x8; + case SimdType::Int32x4: + case SimdType::Uint32x4: return Scalar::Int32x4; + default: MOZ_CRASH("unexpected simd type"); + } +} + +bool +IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType, MInstruction** elements, + MDefinition** index, Scalar::Type* arrayType) +{ + MDefinition* array = callInfo.getArg(0); + *index = callInfo.getArg(1); + + if (!ElementAccessIsTypedArray(constraints(), array, *index, arrayType)) + return false; + + MInstruction* indexAsInt32 = MToInt32::New(alloc(), *index); + current->add(indexAsInt32); + *index = indexAsInt32; + + MDefinition* indexForBoundsCheck = *index; + + // Artificially make sure the index is in bounds by adding the difference + // number of slots needed (e.g. reading from Float32Array we need to make + // sure to be in bounds for 4 slots, so add 3, etc.). + MOZ_ASSERT(Scalar::byteSize(simdType) % Scalar::byteSize(*arrayType) == 0); + int32_t suppSlotsNeeded = Scalar::byteSize(simdType) / Scalar::byteSize(*arrayType) - 1; + if (suppSlotsNeeded) { + MConstant* suppSlots = constant(Int32Value(suppSlotsNeeded)); + MAdd* addedIndex = MAdd::New(alloc(), *index, suppSlots); + // We're fine even with the add overflows, as long as the generated code + // for the bounds check uses an unsigned comparison. + addedIndex->setInt32Specialization(); + current->add(addedIndex); + indexForBoundsCheck = addedIndex; + } + + MInstruction* length; + addTypedArrayLengthAndData(array, SkipBoundsCheck, index, &length, elements); + + // It can be that the index is out of bounds, while the added index for the + // bounds check is in bounds, so we actually need two bounds checks here. + MInstruction* positiveCheck = MBoundsCheck::New(alloc(), *index, length); + current->add(positiveCheck); + + MInstruction* fullCheck = MBoundsCheck::New(alloc(), indexForBoundsCheck, length); + current->add(fullCheck); + return true; +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdLoad(CallInfo& callInfo, JSNative native, SimdType type, unsigned numElems) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + Scalar::Type elemType = SimdTypeToArrayElementType(type); + + MDefinition* index = nullptr; + MInstruction* elements = nullptr; + Scalar::Type arrayType; + if (!prepareForSimdLoadStore(callInfo, elemType, &elements, &index, &arrayType)) + return InliningStatus_NotInlined; + + MLoadUnboxedScalar* load = MLoadUnboxedScalar::New(alloc(), elements, index, arrayType); + load->setResultType(SimdTypeToMIRType(type)); + load->setSimdRead(elemType, numElems); + + return boxSimd(callInfo, load, templateObj); +} + +IonBuilder::InliningStatus +IonBuilder::inlineSimdStore(CallInfo& callInfo, JSNative native, SimdType type, unsigned numElems) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 3, &templateObj)) + return InliningStatus_NotInlined; + + Scalar::Type elemType = SimdTypeToArrayElementType(type); + + MDefinition* index = nullptr; + MInstruction* elements = nullptr; + Scalar::Type arrayType; + if (!prepareForSimdLoadStore(callInfo, elemType, &elements, &index, &arrayType)) + return InliningStatus_NotInlined; + + MDefinition* valueToWrite = unboxSimd(callInfo.getArg(2), type); + MStoreUnboxedScalar* store = MStoreUnboxedScalar::New(alloc(), elements, index, + valueToWrite, arrayType, + MStoreUnboxedScalar::TruncateInput); + store->setSimdWrite(elemType, numElems); + + current->add(store); + // Produce the original boxed value as our return value. + // This is unlikely to be used, so don't bother reboxing valueToWrite. + current->push(callInfo.getArg(2)); + + callInfo.setImplicitlyUsedUnchecked(); + + if (!resumeAfter(store)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +// Note that SIMD.cpp provides its own JSJitInfo objects for SIMD.foo.* functions. +// The Simd* objects defined here represent SIMD.foo() constructor calls. +// They are encoded with .nativeOp = 0. That is the sub-opcode within the SIMD type. +static_assert(uint16_t(SimdOperation::Constructor) == 0, "Constructor opcode must be 0"); + +#define ADD_NATIVE(native) const JSJitInfo JitInfo_##native { \ + { nullptr }, { uint16_t(InlinableNative::native) }, { 0 }, JSJitInfo::InlinableNative }; + INLINABLE_NATIVE_LIST(ADD_NATIVE) +#undef ADD_NATIVE + +} // namespace jit +} // namespace js |