blob: afe1b7e90d9622ff744297383bd7f284130e2ef3 [file] [log] [blame]
/* -*- 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 "jsmath.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 "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 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::ArrayPop:
return inlineArrayPopShift(callInfo, MArrayPopShift::Pop);
case InlinableNative::ArrayShift:
return inlineArrayPopShift(callInfo, MArrayPopShift::Shift);
case InlinableNative::ArrayPush:
return inlineArrayPush(callInfo);
case InlinableNative::ArrayConcat:
return inlineArrayConcat(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::AtomicsFence:
return inlineAtomicsFence(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::RegExpExec:
return CallResultEscapes(pc) ? inlineRegExpExec(callInfo) : inlineRegExpTest(callInfo);
case InlinableNative::RegExpTest:
return inlineRegExpTest(callInfo);
// String natives.
case InlinableNative::String:
return inlineStringObject(callInfo);
case InlinableNative::StringSplit:
return inlineStringSplit(callInfo);
case InlinableNative::StringCharCodeAt:
return inlineStrCharCodeAt(callInfo);
case InlinableNative::StringFromCharCode:
return inlineStrFromCharCode(callInfo);
case InlinableNative::StringCharAt:
return inlineStrCharAt(callInfo);
case InlinableNative::StringReplace:
return inlineStrReplace(callInfo);
// Object natives.
case InlinableNative::ObjectCreate:
return inlineObjectCreate(callInfo);
// Bound function.
case InlinableNative::CallBoundFunction:
return inlineBoundFunction(callInfo, target);
// SIMD natives.
case InlinableNative::SimdInt32x4:
return inlineSimdInt32x4(callInfo, target->native());
case InlinableNative::SimdFloat32x4:
return inlineSimdFloat32x4(callInfo, target->native());
// 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::IntrinsicToObject:
return inlineToObject(callInfo);
case InlinableNative::IntrinsicIsObject:
return inlineIsObject(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::IntrinsicIsStringIterator:
return inlineHasClass(callInfo, &StringIteratorObject::class_);
case InlinableNative::IntrinsicIsListIterator:
return inlineHasClass(callInfo, &ListIteratorObject::class_);
case InlinableNative::IntrinsicDefineDataProperty:
return inlineDefineDataProperty(callInfo);
// TypedArray intrinsics.
case InlinableNative::IntrinsicIsTypedArray:
return inlineIsTypedArray(callInfo);
case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray:
return inlineIsPossiblyWrappedTypedArray(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;
TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet();
MOZ_ASSERT(callInfo.argc() == 0);
// Try to optimize typed array lengths.
if (thisTypes) {
Scalar::Type type;
type = thisTypes->getTypedArrayType(constraints());
if (type != Scalar::MaxTypedArrayViewType &&
TypedArrayObject::isOriginalLengthGetter(native))
{
MInstruction* length = addTypedArrayLength(callInfo.thisArg());
current->push(length);
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 = compartment->runtime()->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);
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) {
if (callInfo.getArg(0)->type() != MIRType_Int32)
return InliningStatus_NotInlined;
MDefinition* arg = callInfo.getArg(0);
if (!arg->isConstantValue()) {
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->constantValue().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();
MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
current->add(templateConst);
MNewArray* ins = MNewArray::New(alloc(), constraints(), initLength, templateConst,
templateObject->group()->initialHeap(constraints()), pc);
current->add(ins);
current->push(ins);
if (callInfo.argc() >= 2) {
JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject);
for (uint32_t i = 0; i < initLength; i++) {
MDefinition* value = callInfo.getArg(i);
if (!initializeArrayElement(ins, i, value, unboxedType, /* addResumePoint = */ false))
return InliningStatus_Error;
}
MInstruction* setLength = setInitializedLength(ins, 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);
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::inlineArrayConcat(CallInfo& callInfo)
{
if (callInfo.argc() != 1 || callInfo.constructing()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
return InliningStatus_NotInlined;
}
MDefinition* thisArg = convertUnboxedObjects(callInfo.thisArg());
MDefinition* objArg = convertUnboxedObjects(callInfo.getArg(0));
// Ensure |this|, argument and result are objects.
if (getInlineReturnType() != MIRType_Object)
return InliningStatus_NotInlined;
if (thisArg->type() != MIRType_Object)
return InliningStatus_NotInlined;
if (objArg->type() != MIRType_Object)
return InliningStatus_NotInlined;
// |this| and the argument must be dense arrays.
TemporaryTypeSet* thisTypes = thisArg->resultTypeSet();
TemporaryTypeSet* argTypes = objArg->resultTypeSet();
if (!thisTypes || !argTypes)
return InliningStatus_NotInlined;
const Class* thisClasp = thisTypes->getKnownClass(constraints());
if (thisClasp != &ArrayObject::class_ && thisClasp != &UnboxedArrayObject::class_)
return InliningStatus_NotInlined;
bool unboxedThis = (thisClasp == &UnboxedArrayObject::class_);
if (thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES |
OBJECT_FLAG_LENGTH_OVERFLOW))
{
trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
return InliningStatus_NotInlined;
}
const Class* argClasp = argTypes->getKnownClass(constraints());
if (argClasp != &ArrayObject::class_ && argClasp != &UnboxedArrayObject::class_)
return InliningStatus_NotInlined;
bool unboxedArg = (argClasp == &UnboxedArrayObject::class_);
if (argTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES |
OBJECT_FLAG_LENGTH_OVERFLOW))
{
trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
return InliningStatus_NotInlined;
}
// Watch out for indexed properties on the prototype.
if (ArrayPrototypeHasIndexedProperty(this, script())) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
return InliningStatus_NotInlined;
}
// Require the 'this' types to have a specific type matching the current
// global, so we can create the result object inline.
if (thisTypes->getObjectCount() != 1)
return InliningStatus_NotInlined;
ObjectGroup* thisGroup = thisTypes->getGroup(0);
if (!thisGroup)
return InliningStatus_NotInlined;
TypeSet::ObjectKey* thisKey = TypeSet::ObjectKey::get(thisGroup);
if (thisKey->unknownProperties())
return InliningStatus_NotInlined;
// Don't inline if 'this' is packed and the argument may not be packed
// (the result array will reuse the 'this' type).
if (!thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED) &&
argTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED))
{
trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
return InliningStatus_NotInlined;
}
// Constraints modeling this concat have not been generated by inference,
// so check that type information already reflects possible side effects of
// this call.
HeapTypeSetKey thisElemTypes = thisKey->property(JSID_VOID);
TemporaryTypeSet* resTypes = getInlineReturnTypeSet();
if (!resTypes->hasType(TypeSet::ObjectType(thisKey)))
return InliningStatus_NotInlined;
for (unsigned i = 0; i < argTypes->getObjectCount(); i++) {
TypeSet::ObjectKey* key = argTypes->getObject(i);
if (!key)
continue;
if (key->unknownProperties())
return InliningStatus_NotInlined;
HeapTypeSetKey elemTypes = key->property(JSID_VOID);
if (!elemTypes.knownSubset(constraints(), thisElemTypes))
return InliningStatus_NotInlined;
if (thisGroup->clasp() == &UnboxedArrayObject::class_ &&
!CanStoreUnboxedType(alloc(), thisGroup->unboxedLayout().elementType(),
MIRType_Value, elemTypes.maybeTypes()))
{
return InliningStatus_NotInlined;
}
}
// Inline the call.
JSObject* templateObj = inspector->getTemplateObjectForNative(pc, js::array_concat);
if (!templateObj || templateObj->group() != thisGroup)
return InliningStatus_NotInlined;
callInfo.setImplicitlyUsedUnchecked();
MArrayConcat* ins = MArrayConcat::New(alloc(), constraints(), thisArg, objArg,
templateObj,
templateObj->group()->initialHeap(constraints()),
unboxedThis, unboxedArg);
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;
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));
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));
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::inlineMathPowHelper(MDefinition* lhs, MDefinition* rhs, MIRType outputType)
{
// Typechecking.
MIRType baseType = lhs->type();
MIRType powerType = rhs->type();
if (outputType != MIRType_Int32 && outputType != MIRType_Double)
return InliningStatus_NotInlined;
if (!IsNumberType(baseType))
return InliningStatus_NotInlined;
if (!IsNumberType(powerType))
return InliningStatus_NotInlined;
MDefinition* base = lhs;
MDefinition* power = rhs;
MDefinition* output = nullptr;
// Optimize some constant powers.
if (rhs->isConstantValue() && rhs->constantValue().isNumber()) {
double pow = rhs->constantValue().toNumber();
// Math.pow(x, 0.5) is a sqrt with edge-case detection.
if (pow == 0.5) {
MPowHalf* half = MPowHalf::New(alloc(), base);
current->add(half);
output = half;
}
// Math.pow(x, -0.5) == 1 / Math.pow(x, 0.5), even for edge cases.
if (pow == -0.5) {
MPowHalf* half = MPowHalf::New(alloc(), base);
current->add(half);
MConstant* one = MConstant::New(alloc(), DoubleValue(1.0));
current->add(one);
MDiv* div = MDiv::New(alloc(), one, half, MIRType_Double);
current->add(div);
output = div;
}
// Math.pow(x, 1) == x.
if (pow == 1.0)
output = base;
// Math.pow(x, 2) == x*x.
if (pow == 2.0) {
MMul* mul = MMul::New(alloc(), base, base, outputType);
current->add(mul);
output = mul;
}
// Math.pow(x, 3) == x*x*x.
if (pow == 3.0) {
MMul* mul1 = MMul::New(alloc(), base, base, outputType);
current->add(mul1);
MMul* mul2 = MMul::New(alloc(), base, mul1, outputType);
current->add(mul2);
output = mul2;
}
// Math.pow(x, 4) == y*y, where y = x*x.
if (pow == 4.0) {
MMul* y = MMul::New(alloc(), base, base, outputType);
current->add(y);
MMul* mul = MMul::New(alloc(), y, y, outputType);
current->add(mul);
output = mul;
}
}
// Use MPow for other powers
if (!output) {
if (powerType == MIRType_Float32)
powerType = MIRType_Double;
MPow* pow = MPow::New(alloc(), base, power, powerType);
current->add(pow);
output = pow;
}
// Cast to the right type
if (outputType == MIRType_Int32 && output->type() != MIRType_Int32) {
MToInt32* toInt = MToInt32::New(alloc(), output);
current->add(toInt);
output = toInt;
}
if (outputType == MIRType_Double && output->type() != MIRType_Double) {
MToDouble* toDouble = MToDouble::New(alloc(), output);
current->add(toDouble);
output = toDouble;
}
current->push(output);
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineMathPow(CallInfo& callInfo)
{
if (callInfo.argc() != 2 || callInfo.constructing()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
return InliningStatus_NotInlined;
}
IonBuilder::InliningStatus status =
inlineMathPowHelper(callInfo.getArg(0), callInfo.getArg(1), getInlineReturnType());
if (status == IonBuilder::InliningStatus_Inlined)
callInfo.setImplicitlyUsedUnchecked();
return status;
}
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->isConstantValue()) {
double cte = arg->constantValue().toDouble();
// 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::inlineConstantStringSplit(CallInfo& callInfo)
{
if (!callInfo.thisArg()->isConstant())
return InliningStatus_NotInlined;
if (!callInfo.getArg(0)->isConstant())
return InliningStatus_NotInlined;
const js::Value* argval = callInfo.getArg(0)->toConstant()->vp();
if (!argval->isString())
return InliningStatus_NotInlined;
const js::Value* strval = callInfo.thisArg()->toConstant()->vp();
if (!strval->isString())
return InliningStatus_NotInlined;
MOZ_ASSERT(callInfo.getArg(0)->type() == MIRType_String);
MOZ_ASSERT(callInfo.thisArg()->type() == MIRType_String);
// Check if exist a template object in stub.
JSString* stringThis = nullptr;
JSString* stringArg = nullptr;
JSObject* templateObject = nullptr;
if (!inspector->isOptimizableCallStringSplit(pc, &stringThis, &stringArg, &templateObject))
return InliningStatus_NotInlined;
MOZ_ASSERT(stringThis);
MOZ_ASSERT(stringArg);
MOZ_ASSERT(templateObject);
if (strval->toString() != stringThis)
return InliningStatus_NotInlined;
if (argval->toString() != stringArg)
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(), str, constraints());
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;
MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
current->add(templateConst);
MNewArray* ins = MNewArray::New(alloc(), constraints(), initLength, templateConst,
templateObject->group()->initialHeap(constraints()), pc);
current->add(ins);
current->push(ins);
if (!initLength) {
if (!resumeAfter(ins))
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++) {
MConstant* value = arrayValues[i];
current->add(value);
if (!initializeArrayElement(ins, i, value, unboxedType, /* addResumePoint = */ false))
return InliningStatus_Error;
}
MInstruction* setLength = setInitializedLength(ins, unboxedType, initLength);
if (!resumeAfter(setLength))
return InliningStatus_Error;
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineStringSplit(CallInfo& callInfo)
{
if (callInfo.argc() != 1 || callInfo.constructing()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
return InliningStatus_NotInlined;
}
if (callInfo.thisArg()->type() != MIRType_String)
return InliningStatus_NotInlined;
if (callInfo.getArg(0)->type() != MIRType_String)
return InliningStatus_NotInlined;
IonBuilder::InliningStatus resultConstStringSplit = inlineConstantStringSplit(callInfo);
if (resultConstStringSplit != InliningStatus_NotInlined)
return resultConstStringSplit;
JSObject* templateObject = inspector->getTemplateObjectForNative(pc, js::str_split);
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(), callInfo.thisArg(),
callInfo.getArg(0), templateObjectDef);
current->add(ins);
current->push(ins);
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()->isConstantValue() || !callInfo.getArg(0)->isConstantValue()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric);
return InliningStatus_NotInlined;
}
const js::Value* strval = callInfo.thisArg()->constantVp();
const js::Value* idxval = callInfo.getArg(0)->constantVp();
if (!strval->isString() || !idxval->isInt32())
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();
MToInt32* charCode = MToInt32::New(alloc(), callInfo.getArg(0));
current->add(charCode);
MFromCharCode* string = MFromCharCode::New(alloc(), charCode);
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::inlineRegExpExec(CallInfo& callInfo)
{
if (callInfo.argc() != 1 || callInfo.constructing()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
return InliningStatus_NotInlined;
}
if (callInfo.thisArg()->type() != MIRType_Object)
return InliningStatus_NotInlined;
TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet();
const Class* clasp = thisTypes ? thisTypes->getKnownClass(constraints()) : nullptr;
if (clasp != &RegExpObject::class_)
return InliningStatus_NotInlined;
if (callInfo.getArg(0)->mightBeType(MIRType_Object))
return InliningStatus_NotInlined;
JSContext* cx = GetJitContext()->cx;
if (!cx->compartment()->jitCompartment()->ensureRegExpExecStubExists(cx))
return InliningStatus_Error;
callInfo.setImplicitlyUsedUnchecked();
MInstruction* exec = MRegExpExec::New(alloc(), callInfo.thisArg(), callInfo.getArg(0));
current->add(exec);
current->push(exec);
if (!resumeAfter(exec))
return InliningStatus_Error;
if (!pushTypeBarrier(exec, getInlineReturnTypeSet(), BarrierKind::TypeSet))
return InliningStatus_Error;
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineRegExpTest(CallInfo& callInfo)
{
if (callInfo.argc() != 1 || callInfo.constructing()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
return InliningStatus_NotInlined;
}
// TI can infer a nullptr return type of regexp_test with eager compilation.
if (CallResultEscapes(pc) && getInlineReturnType() != MIRType_Boolean)
return InliningStatus_NotInlined;
if (callInfo.thisArg()->type() != MIRType_Object)
return InliningStatus_NotInlined;
TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet();
const Class* clasp = thisTypes ? thisTypes->getKnownClass(constraints()) : nullptr;
if (clasp != &RegExpObject::class_)
return InliningStatus_NotInlined;
if (callInfo.getArg(0)->mightBeType(MIRType_Object))
return InliningStatus_NotInlined;
JSContext* cx = GetJitContext()->cx;
if (!cx->compartment()->jitCompartment()->ensureRegExpTestStubExists(cx))
return InliningStatus_Error;
callInfo.setImplicitlyUsedUnchecked();
MInstruction* match = MRegExpTest::New(alloc(), callInfo.thisArg(), callInfo.getArg(0));
current->add(match);
current->push(match);
if (!resumeAfter(match))
return InliningStatus_Error;
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineStrReplace(CallInfo& callInfo)
{
if (callInfo.argc() != 2 || callInfo.constructing()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
return InliningStatus_NotInlined;
}
// Return: String.
if (getInlineReturnType() != MIRType_String)
return InliningStatus_NotInlined;
// This: String.
if (callInfo.thisArg()->type() != MIRType_String)
return InliningStatus_NotInlined;
// Arg 0: RegExp.
TemporaryTypeSet* arg0Type = callInfo.getArg(0)->resultTypeSet();
const Class* clasp = arg0Type ? arg0Type->getKnownClass(constraints()) : nullptr;
if (clasp != &RegExpObject::class_ && callInfo.getArg(0)->type() != MIRType_String)
return InliningStatus_NotInlined;
// Arg 1: String.
if (callInfo.getArg(1)->type() != MIRType_String)
return InliningStatus_NotInlined;
callInfo.setImplicitlyUsedUnchecked();
MInstruction* cte;
if (callInfo.getArg(0)->type() == MIRType_String) {
cte = MStringReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0),
callInfo.getArg(1));
} else {
cte = MRegExpReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0),
callInfo.getArg(1));
}
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->getProto()) {
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();
MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
current->add(templateConst);
MNewObject* ins = MNewObject::New(alloc(), constraints(), templateConst,
templateObject->group()->initialHeap(constraints()),
MNewObject::ObjectCreate);
current->add(ins);
current->push(ins);
if (!resumeAfter(ins))
return InliningStatus_Error;
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;
}
// Convert to bool with the '!!' idiom
MNot* resultInverted = MNot::New(alloc(), last, constraints());
current->add(resultInverted);
MNot* result = MNot::New(alloc(), resultInverted, constraints());
current->add(result);
current->push(result);
}
}
callInfo.setImplicitlyUsedUnchecked();
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:
case TemporaryTypeSet::ForAllResult::EMPTY: {
// 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)
return InliningStatus_NotInlined;
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::inlineTypedArrayLength(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;
// Note that the argument we see here is not necessarily a typed array.
// If it's not, this call should be unreachable though.
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::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->isConstantValue())
return InliningStatus_NotInlined;
uint32_t slot = arg->constantValue().toPrivateUint32();
callInfo.setImplicitlyUsedUnchecked();
MStoreFixedSlot* store = MStoreFixedSlot::New(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->isConstantValue())
return InliningStatus_NotInlined;
uint32_t slot = arg->constantValue().toPrivateUint32();
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;
if (callInfo.getArg(0)->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 (callInfo.getArg(0)->type() != MIRType_Object) {
isCallableKnown = true;
isCallableConstant = false;
} else {
TemporaryTypeSet* types = callInfo.getArg(0)->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(), callInfo.getArg(0));
current->add(isCallable);
current->push(isCallable);
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::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;