Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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 "builtin/AtomicsObject.h"
#include "builtin/DataViewObject.h"
#include "builtin/MapObject.h"
#include "builtin/String.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 "js/experimental/JitInfo.h" // JSJitInfo
#include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
#include "js/ScalarType.h" // js::Scalar::Type
#include "vm/ArgumentsObject.h"
#include "vm/ArrayBufferObject.h"
#include "vm/JSObject.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/ProxyObject.h"
#include "vm/SelfHosting.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"
#include "wasm/WasmInstance.h"
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/StringObject-inl.h"
using mozilla::ArrayLength;
using mozilla::AssertedCast;
using mozilla::Maybe;
using JS::RegExpFlag;
using JS::RegExpFlags;
namespace js {
namespace jit {
IonBuilder::InliningResult IonBuilder::inlineNativeCall(CallInfo& callInfo,
JSFunction* target) {
MOZ_ASSERT(target->isNative());
if (!optimizationInfo().inlineNative()) {
return InliningStatus_NotInlined;
}
bool isWasmCall = target->isWasmWithJitEntry();
if (!isWasmCall &&
(!target->hasJitInfo() ||
target->jitInfo()->type() != JSJitInfo::InlinableNative)) {
// Reaching here means we tried to inline a native for which there is no
// Ion specialization.
return InliningStatus_NotInlined;
}
// Don't inline if we're constructing and new.target != callee. This can
// happen with Reflect.construct or derived class constructors.
if (callInfo.constructing() && callInfo.getNewTarget() != callInfo.callee()) {
return InliningStatus_NotInlined;
}
if (shouldAbortOnPreliminaryGroups(callInfo.thisArg())) {
return InliningStatus_NotInlined;
}
for (size_t i = 0; i < callInfo.argc(); i++) {
if (shouldAbortOnPreliminaryGroups(callInfo.getArg(i))) {
return InliningStatus_NotInlined;
}
}
if (isWasmCall) {
return inlineWasmCall(callInfo, target);
}
InlinableNative inlNative = target->jitInfo()->inlinableNative;
if (target->realm() != script()->realm() &&
!CanInlineNativeCrossRealm(inlNative)) {
return InliningStatus_NotInlined;
}
switch (inlNative) {
// Array natives.
case InlinableNative::Array:
return inlineArray(callInfo, target->realm());
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);
// Array intrinsics.
case InlinableNative::IntrinsicNewArrayIterator:
return inlineNewIterator(callInfo, MNewIterator::ArrayIterator);
case InlinableNative::IntrinsicArrayIteratorPrototypeOptimizable:
return inlineArrayIteratorPrototypeOptimizable(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);
// Boolean natives.
case InlinableNative::Boolean:
return inlineBoolean(callInfo);
// DataView natives.
case InlinableNative::DataViewGetInt8:
return inlineDataViewGet(callInfo, Scalar::Int8);
case InlinableNative::DataViewGetUint8:
return inlineDataViewGet(callInfo, Scalar::Uint8);
case InlinableNative::DataViewGetInt16:
return inlineDataViewGet(callInfo, Scalar::Int16);
case InlinableNative::DataViewGetUint16:
return inlineDataViewGet(callInfo, Scalar::Uint16);
case InlinableNative::DataViewGetInt32:
return inlineDataViewGet(callInfo, Scalar::Int32);
case InlinableNative::DataViewGetUint32:
return inlineDataViewGet(callInfo, Scalar::Uint32);
case InlinableNative::DataViewGetFloat32:
return inlineDataViewGet(callInfo, Scalar::Float32);
case InlinableNative::DataViewGetFloat64:
return inlineDataViewGet(callInfo, Scalar::Float64);
case InlinableNative::DataViewGetBigInt64:
return inlineDataViewGet(callInfo, Scalar::BigInt64);
case InlinableNative::DataViewGetBigUint64:
return inlineDataViewGet(callInfo, Scalar::BigUint64);
case InlinableNative::DataViewSetInt8:
return inlineDataViewSet(callInfo, Scalar::Int8);
case InlinableNative::DataViewSetUint8:
return inlineDataViewSet(callInfo, Scalar::Uint8);
case InlinableNative::DataViewSetInt16:
return inlineDataViewSet(callInfo, Scalar::Int16);
case InlinableNative::DataViewSetUint16:
return inlineDataViewSet(callInfo, Scalar::Uint16);
case InlinableNative::DataViewSetInt32:
return inlineDataViewSet(callInfo, Scalar::Int32);
case InlinableNative::DataViewSetUint32:
return inlineDataViewSet(callInfo, Scalar::Uint32);
case InlinableNative::DataViewSetFloat32:
return inlineDataViewSet(callInfo, Scalar::Float32);
case InlinableNative::DataViewSetFloat64:
return inlineDataViewSet(callInfo, Scalar::Float64);
case InlinableNative::DataViewSetBigInt64:
return inlineDataViewSet(callInfo, Scalar::BigInt64);
case InlinableNative::DataViewSetBigUint64:
return inlineDataViewSet(callInfo, Scalar::BigUint64);
#ifdef JS_HAS_INTL_API
// Intl natives.
case InlinableNative::IntlGuardToCollator:
case InlinableNative::IntlGuardToDateTimeFormat:
case InlinableNative::IntlGuardToDisplayNames:
case InlinableNative::IntlGuardToListFormat:
case InlinableNative::IntlGuardToNumberFormat:
case InlinableNative::IntlGuardToPluralRules:
case InlinableNative::IntlGuardToRelativeTimeFormat:
return inlineGuardToClass(callInfo, inlNative);
#else
case InlinableNative::IntlGuardToCollator:
case InlinableNative::IntlGuardToDateTimeFormat:
case InlinableNative::IntlGuardToDisplayNames:
case InlinableNative::IntlGuardToListFormat:
case InlinableNative::IntlGuardToNumberFormat:
case InlinableNative::IntlGuardToPluralRules:
case InlinableNative::IntlGuardToRelativeTimeFormat:
MOZ_CRASH("Intl API disabled");
#endif
// 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::MathTrunc:
return inlineMathTrunc(callInfo);
case InlinableNative::MathSign:
return inlineMathSign(callInfo);
case InlinableNative::MathSin:
return inlineMathFunction(callInfo, UnaryMathFunction::Sin);
case InlinableNative::MathTan:
return inlineMathFunction(callInfo, UnaryMathFunction::Tan);
case InlinableNative::MathCos:
return inlineMathFunction(callInfo, UnaryMathFunction::Cos);
case InlinableNative::MathExp:
return inlineMathFunction(callInfo, UnaryMathFunction::Exp);
case InlinableNative::MathLog:
return inlineMathFunction(callInfo, UnaryMathFunction::Log);
case InlinableNative::MathASin:
return inlineMathFunction(callInfo, UnaryMathFunction::ASin);
case InlinableNative::MathATan:
return inlineMathFunction(callInfo, UnaryMathFunction::ATan);
case InlinableNative::MathACos:
return inlineMathFunction(callInfo, UnaryMathFunction::ACos);
case InlinableNative::MathLog10:
return inlineMathFunction(callInfo, UnaryMathFunction::Log10);
case InlinableNative::MathLog2:
return inlineMathFunction(callInfo, UnaryMathFunction::Log2);
case InlinableNative::MathLog1P:
return inlineMathFunction(callInfo, UnaryMathFunction::Log1P);
case InlinableNative::MathExpM1:
return inlineMathFunction(callInfo, UnaryMathFunction::ExpM1);
case InlinableNative::MathCosH:
return inlineMathFunction(callInfo, UnaryMathFunction::CosH);
case InlinableNative::MathSinH:
return inlineMathFunction(callInfo, UnaryMathFunction::SinH);
case InlinableNative::MathTanH:
return inlineMathFunction(callInfo, UnaryMathFunction::TanH);
case InlinableNative::MathACosH:
return inlineMathFunction(callInfo, UnaryMathFunction::ACosH);
case InlinableNative::MathASinH:
return inlineMathFunction(callInfo, UnaryMathFunction::ASinH);
case InlinableNative::MathATanH:
return inlineMathFunction(callInfo, UnaryMathFunction::ATanH);
case InlinableNative::MathCbrt:
return inlineMathFunction(callInfo, UnaryMathFunction::Cbrt);
// Reflect natives.
case InlinableNative::ReflectGetPrototypeOf:
return inlineReflectGetPrototypeOf(callInfo);
// 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::IsPossiblyWrappedRegExpObject:
return inlineIsPossiblyWrappedRegExpObject(callInfo);
case InlinableNative::RegExpPrototypeOptimizable:
return inlineRegExpPrototypeOptimizable(callInfo);
case InlinableNative::RegExpInstanceOptimizable:
return inlineRegExpInstanceOptimizable(callInfo);
case InlinableNative::GetFirstDollarIndex:
return inlineGetFirstDollarIndex(callInfo);
case InlinableNative::IntrinsicNewRegExpStringIterator:
return inlineNewIterator(callInfo, MNewIterator::RegExpStringIterator);
// 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);
case InlinableNative::StringToLowerCase:
return inlineStringConvertCase(callInfo, MStringConvertCase::LowerCase);
case InlinableNative::StringToUpperCase:
return inlineStringConvertCase(callInfo, MStringConvertCase::UpperCase);
// String intrinsics.
case InlinableNative::IntrinsicStringReplaceString:
return inlineStringReplaceString(callInfo);
case InlinableNative::IntrinsicStringSplitString:
return inlineStringSplitString(callInfo);
case InlinableNative::IntrinsicNewStringIterator:
return inlineNewIterator(callInfo, MNewIterator::StringIterator);
// Object natives.
case InlinableNative::Object:
return inlineObject(callInfo);
case InlinableNative::ObjectCreate:
return inlineObjectCreate(callInfo);
case InlinableNative::ObjectIs:
return inlineObjectIs(callInfo);
case InlinableNative::ObjectIsPrototypeOf:
return inlineObjectIsPrototypeOf(callInfo);
case InlinableNative::ObjectToString:
return inlineObjectToString(callInfo);
// 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::IntrinsicIsCrossRealmArrayConstructor:
return inlineIsCrossRealmArrayConstructor(callInfo);
case InlinableNative::IntrinsicToInteger:
return inlineToInteger(callInfo);
case InlinableNative::IntrinsicToLength:
return inlineToLength(callInfo);
case InlinableNative::IntrinsicIsConstructing:
return inlineIsConstructing(callInfo);
case InlinableNative::IntrinsicSubstringKernel:
return inlineSubstringKernel(callInfo);
case InlinableNative::IntrinsicGuardToArrayIterator:
case InlinableNative::IntrinsicGuardToMapIterator:
case InlinableNative::IntrinsicGuardToSetIterator:
case InlinableNative::IntrinsicGuardToStringIterator:
case InlinableNative::IntrinsicGuardToRegExpStringIterator:
case InlinableNative::IntrinsicGuardToWrapForValidIterator:
case InlinableNative::IntrinsicGuardToIteratorHelper:
case InlinableNative::IntrinsicGuardToAsyncIteratorHelper:
return inlineGuardToClass(callInfo, inlNative);
case InlinableNative::IntrinsicObjectHasPrototype:
return inlineObjectHasPrototype(callInfo);
case InlinableNative::IntrinsicFinishBoundFunctionInit:
return inlineFinishBoundFunctionInit(callInfo);
case InlinableNative::IntrinsicIsPackedArray:
return inlineIsPackedArray(callInfo);
// Map intrinsics.
case InlinableNative::IntrinsicGuardToMapObject:
return inlineGuardToClass(callInfo, inlNative);
case InlinableNative::IntrinsicGetNextMapEntryForIterator:
return inlineGetNextEntryForIterator(callInfo,
MGetNextEntryForIterator::Map);
// Set intrinsics.
case InlinableNative::IntrinsicGuardToSetObject:
return inlineGuardToClass(callInfo, inlNative);
case InlinableNative::IntrinsicGetNextSetEntryForIterator:
return inlineGetNextEntryForIterator(callInfo,
MGetNextEntryForIterator::Set);
// ArrayBuffer intrinsics.
case InlinableNative::IntrinsicGuardToArrayBuffer:
return inlineGuardToClass(callInfo, inlNative);
case InlinableNative::IntrinsicArrayBufferByteLength:
return inlineArrayBufferByteLength(callInfo);
case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength:
return inlinePossiblyWrappedArrayBufferByteLength(callInfo);
// SharedArrayBuffer intrinsics.
case InlinableNative::IntrinsicGuardToSharedArrayBuffer:
return inlineGuardToClass(callInfo, inlNative);
// TypedArray intrinsics.
case InlinableNative::TypedArrayConstructor:
return inlineTypedArray(callInfo, target->native());
case InlinableNative::IntrinsicIsTypedArrayConstructor:
return inlineIsTypedArrayConstructor(callInfo);
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::IntrinsicTypedArrayByteOffset:
return inlineTypedArrayByteOffset(callInfo);
case InlinableNative::IntrinsicTypedArrayElementShift:
return inlineTypedArrayElementShift(callInfo);
case InlinableNative::NumberToString:
case InlinableNative::StringToString:
case InlinableNative::StringValueOf:
case InlinableNative::IntrinsicIsSuspendedGenerator:
// Not supported in Ion.
return InliningStatus_NotInlined;
case InlinableNative::Limit:
break;
}
MOZ_CRASH("Shouldn't get here");
}
IonBuilder::InliningResult 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;
}
// Note: target might be a cross-realm native!
// Try to optimize typed array lengths.
if (TypedArrayObject::isOriginalLengthGetter(native)) {
if (thisTypes->forAllClasses(constraints(), IsTypedArrayClass) !=
TemporaryTypeSet::ForAllResult::ALL_TRUE) {
return InliningStatus_NotInlined;
}
MInstruction* length = addTypedArrayLength(thisArg);
current->push(length);
return InliningStatus_Inlined;
}
// Try to optimize typed array byteOffsets.
if (TypedArrayObject::isOriginalByteOffsetGetter(native)) {
if (thisTypes->forAllClasses(constraints(), IsTypedArrayClass) !=
TemporaryTypeSet::ForAllResult::ALL_TRUE) {
return InliningStatus_NotInlined;
}
MInstruction* byteOffset = addTypedArrayByteOffset(thisArg);
current->push(byteOffset);
return InliningStatus_Inlined;
}
// Try to optimize DataView byteLengths.
if (DataViewObject::isOriginalByteLengthGetter(native)) {
const JSClass* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &DataViewObject::class_) {
return InliningStatus_NotInlined;
}
auto* length = MArrayBufferViewLength::New(alloc(), thisArg);
current->add(length);
current->push(length);
return InliningStatus_Inlined;
}
// Try to optimize DataView byteOffsets.
if (DataViewObject::isOriginalByteOffsetGetter(native)) {
const JSClass* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &DataViewObject::class_) {
return InliningStatus_NotInlined;
}
auto* byteOffset = MArrayBufferViewByteOffset::New(alloc(), thisArg);
current->add(byteOffset);
current->push(byteOffset);
return InliningStatus_Inlined;
}
// Try to optimize RegExp getters.
RegExpFlags mask = RegExpFlag::NoFlags;
if (RegExpObject::isOriginalFlagGetter(native, &mask)) {
const JSClass* 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.value()));
current->add(maskConst);
auto* maskedFlag = MBitAnd::New(alloc(), flags, maskConst, MIRType::Int32);
current->add(maskedFlag);
MDefinition* result = convertToBoolean(maskedFlag);
current->push(result);
return InliningStatus_Inlined;
}
return InliningStatus_NotInlined;
}
TemporaryTypeSet* IonBuilder::getInlineReturnTypeSet() {
return bytecodeTypes(pc);
}
MIRType IonBuilder::getInlineReturnType() {
TemporaryTypeSet* returnTypes = getInlineReturnTypeSet();
return returnTypes->getKnownMIRType();
}
IonBuilder::InliningResult IonBuilder::inlineMathFunction(
CallInfo& callInfo, UnaryMathFunction 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;
}
callInfo.callee()->setImplicitlyUsedUnchecked();
callInfo.thisArg()->setImplicitlyUsedUnchecked();
MMathFunction* ins =
MMathFunction::New(alloc(), callInfo.getArg(0), function);
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineArray(CallInfo& callInfo,
Realm* targetRealm) {
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) {
return InliningStatus_NotInlined;
}
if (templateObject->nonCCWRealm() != targetRealm) {
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);
// This may throw, so we need a resume point.
MOZ_TRY(resumeAfter(ins));
return InliningStatus_Inlined;
}
// 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 != templateObject->as<ArrayObject>().length()) {
return InliningStatus_NotInlined;
}
// Don't inline large allocations.
if (initLength > ArrayObject::EagerAllocationMaxLength) {
return InliningStatus_NotInlined;
}
}
callInfo.setImplicitlyUsedUnchecked();
MOZ_TRY(jsop_newarray(templateObject, initLength));
MNewArray* array = current->peek(-1)->toNewArray();
if (callInfo.argc() >= 2) {
for (uint32_t i = 0; i < initLength; i++) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
MDefinition* value = callInfo.getArg(i);
MConstant* id = MConstant::New(alloc(), Int32Value(i));
current->add(id);
MOZ_TRY(initArrayElementFastPath(array, id, value,
/* addResumePoint = */ false));
}
MInstruction* setLength = setInitializedLength(array, initLength);
MOZ_TRY(resumeAfter(setLength));
}
return InliningStatus_Inlined;
}
static bool IsArrayClass(const JSClass* clasp) {
return clasp == &ArrayObject::class_;
}
IonBuilder::InliningResult IonBuilder::inlineArrayIsArray(CallInfo& callInfo) {
if (callInfo.constructing() || callInfo.argc() != 1) {
return InliningStatus_NotInlined;
}
if (getInlineReturnType() != MIRType::Boolean) {
return InliningStatus_NotInlined;
}
MDefinition* arg = callInfo.getArg(0);
if (!arg->mightBeType(MIRType::Object)) {
pushConstant(BooleanValue(false));
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
}
using ForAllResult = TemporaryTypeSet::ForAllResult;
TemporaryTypeSet* types = arg->resultTypeSet();
// Fast path for non-proxy objects.
if (arg->type() == MIRType::Object && types &&
types->forAllClasses(constraints(), IsProxyClass) ==
ForAllResult::ALL_FALSE) {
// Definitely not a proxy. Now check for the array classes.
ForAllResult result = types->forAllClasses(constraints(), IsArrayClass);
if (result == ForAllResult::ALL_FALSE || result == ForAllResult::ALL_TRUE) {
// Definitely an array or definitely not an array, so we can
// constant fold.
pushConstant(BooleanValue(result == ForAllResult::ALL_TRUE));
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
}
// We have some array classes and some non-array classes, so we have to
// check at runtime.
MOZ_ASSERT(result == ForAllResult::MIXED);
MHasClass* hasClass = MHasClass::New(alloc(), arg, &ArrayObject::class_);
current->add(hasClass);
current->push(hasClass);
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
}
// The value might be a primitive or a proxy. MIsArray handles these cases.
MIsArray* isArray = MIsArray::New(alloc(), arg);
current->add(isArray);
current->push(isArray);
MOZ_TRY(resumeAfter(isArray));
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineArrayPopShift(
CallInfo& callInfo, MArrayPopShift::Mode mode) {
if (callInfo.constructing()) {
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.
// Don't optimize shift if the array may be non-extensible (this matters
// when there are holes). Don't optimize pop if the array may be
// non-extensible, so we don't need to adjust the capacity for
// non-extensible arrays (non-extensible objects always have a capacity
// equal to their initialized length). We check this here because there's
// no non-extensible ObjectElements flag so we would need an extra guard
// on the BaseShape flags.
ObjectGroupFlags unhandledFlags =
OBJECT_FLAG_SPARSE_INDEXES | OBJECT_FLAG_LENGTH_OVERFLOW |
OBJECT_FLAG_ITERATED | OBJECT_FLAG_NON_EXTENSIBLE_ELEMENTS;
MDefinition* obj = callInfo.thisArg();
TemporaryTypeSet* thisTypes = obj->resultTypeSet();
if (!thisTypes) {
return InliningStatus_NotInlined;
}
const JSClass* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &ArrayObject::class_) {
return InliningStatus_NotInlined;
}
if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) {
return InliningStatus_NotInlined;
}
// Watch out for extra indexed properties on the object or its prototype.
bool hasIndexedProperty;
MOZ_TRY_VAR(hasIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, obj));
if (hasIndexedProperty) {
return InliningStatus_NotInlined;
}
callInfo.setImplicitlyUsedUnchecked();
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, alloc(), constraints(), obj, nullptr, returnTypes);
if (barrier != BarrierKind::NoBarrier) {
returnType = MIRType::Value;
}
MArrayPopShift* ins =
MArrayPopShift::New(alloc(), obj, mode, needsHoleCheck, maybeUndefined);
current->add(ins);
current->push(ins);
ins->setResultType(returnType);
MOZ_TRY(resumeAfter(ins));
MOZ_TRY(pushTypeBarrier(ins, returnTypes, barrier));
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineArrayJoin(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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;
}
// If we can confirm that the class is an array, the codegen
// for MArrayJoin can be notified to check for common empty and one-item
// arrays.
bool optimizeForArray = ([&]() {
TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet();
if (!thisTypes) {
return false;
}
const JSClass* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &ArrayObject::class_) {
return false;
}
return true;
})();
callInfo.setImplicitlyUsedUnchecked();
MArrayJoin* ins = MArrayJoin::New(alloc(), callInfo.thisArg(),
callInfo.getArg(0), optimizeForArray);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineArrayPush(CallInfo& callInfo) {
const uint32_t inlineArgsLimit = 10;
if (callInfo.argc() < 1 || callInfo.argc() > inlineArgsLimit ||
callInfo.constructing()) {
return InliningStatus_NotInlined;
}
// XXX bug 1493903.
if (callInfo.argc() != 1) {
return InliningStatus_NotInlined;
}
MDefinition* obj = callInfo.thisArg();
for (uint32_t i = 0; i < callInfo.argc(); i++) {
MDefinition* value = callInfo.getArg(i);
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj,
nullptr, &value,
/* canModify = */ false)) {
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 JSClass* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &ArrayObject::class_) {
return InliningStatus_NotInlined;
}
bool hasIndexedProperty;
MOZ_TRY_VAR(hasIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, obj));
if (hasIndexedProperty) {
return InliningStatus_NotInlined;
}
TemporaryTypeSet::DoubleConversion conversion =
thisTypes->convertDoubleElements(constraints());
if (conversion == TemporaryTypeSet::AmbiguousDoubleConversion) {
return InliningStatus_NotInlined;
}
callInfo.setImplicitlyUsedUnchecked();
bool toDouble = conversion == TemporaryTypeSet::AlwaysConvertToDoubles ||
conversion == TemporaryTypeSet::MaybeConvertToDoubles;
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
// If we have more than one argument, we are splitting the function into
// multiple inlined calls to Array.push.
//
// Still, in case of bailouts, we have to keep the atomicity of the call, as
// we cannot resume within Array.push function. To do so, we register a
// resume point which would be captured by the upcoming MArrayPush
// instructions, and this resume point contains an instruction which
// truncates the length of the Array, to its original length in case of
// bailouts, and resume before the Array.push call.
MResumePoint* lastRp = nullptr;
MInstruction* truncate = nullptr;
if (callInfo.argc() > 1) {
MInstruction* elements = MElements::New(alloc(), obj);
MInstruction* length = MArrayLength::New(alloc(), elements);
truncate = MSetArrayLength::New(alloc(), obj, length);
truncate->setRecoveredOnBailout();
current->add(elements);
current->add(length);
current->add(truncate);
// Restore the stack, such that resume points are created with the stack
// as it was before the call.
if (!callInfo.pushPriorCallStack(&mirGen_, current)) {
return abort(AbortReason::Alloc);
}
}
MInstruction* ins = nullptr;
for (uint32_t i = 0; i < callInfo.argc(); i++) {
MDefinition* value = callInfo.getArg(i);
if (toDouble) {
MInstruction* valueDouble = MToDouble::New(alloc(), value);
current->add(valueDouble);
value = valueDouble;
}
if (needsPostBarrier(value)) {
MInstruction* elements = MElements::New(alloc(), obj);
current->add(elements);
MInstruction* initLength = MInitializedLength::New(alloc(), elements);
current->add(initLength);
current->add(
MPostWriteElementBarrier::New(alloc(), obj, value, initLength));
}
ins = MArrayPush::New(alloc(), obj, value);
current->add(ins);
if (callInfo.argc() > 1) {
// Restore that call stack and the array length.
MOZ_TRY(resumeAt(ins, pc));
ins->resumePoint()->addStore(alloc(), truncate, lastRp);
lastRp = ins->resumePoint();
}
}
if (callInfo.argc() > 1) {
// Fix the stack to represent the state after the call execution.
callInfo.popPriorCallStack(current);
}
current->push(ins);
if (callInfo.argc() > 1) {
ins = MNop::New(alloc());
current->add(ins);
}
MOZ_TRY(resumeAfter(ins));
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineArraySlice(CallInfo& callInfo) {
if (callInfo.constructing()) {
return InliningStatus_NotInlined;
}
MDefinition* obj = 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 JSClass* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &ArrayObject::class_) {
return InliningStatus_NotInlined;
}
// Watch out for extra indexed properties on the object or its prototype.
bool hasIndexedProperty;
MOZ_TRY_VAR(hasIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, obj));
if (hasIndexedProperty) {
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 (!templateObj->is<ArrayObject>()) {
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());
}
MArraySlice* ins =
MArraySlice::New(alloc(), obj, begin, end, templateObj,
templateObj->group()->initialHeap(constraints()));
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
MOZ_TRY(pushTypeBarrier(ins, getInlineReturnTypeSet(), BarrierKind::TypeSet));
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineBoolean(CallInfo& callInfo) {
if (callInfo.constructing()) {
return InliningStatus_NotInlined;
}
if (getInlineReturnType() != MIRType::Boolean) {
return InliningStatus_NotInlined;
}
callInfo.setImplicitlyUsedUnchecked();
if (callInfo.argc() > 0) {
MDefinition* result = convertToBoolean(callInfo.getArg(0));
current->push(result);
} else {
pushConstant(BooleanValue(false));
}
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineNewIterator(
CallInfo& callInfo, MNewIterator::Type type) {
MOZ_ASSERT(!callInfo.constructing());
MOZ_ASSERT(callInfo.argc() == 0);
JSObject* templateObject = nullptr;
switch (type) {
case MNewIterator::ArrayIterator:
templateObject = inspector->getTemplateObjectForNative(
pc, js::intrinsic_NewArrayIterator);
MOZ_ASSERT_IF(templateObject, templateObject->is<ArrayIteratorObject>());
break;
case MNewIterator::StringIterator:
templateObject = inspector->getTemplateObjectForNative(
pc, js::intrinsic_NewStringIterator);
MOZ_ASSERT_IF(templateObject, templateObject->is<StringIteratorObject>());
break;
case MNewIterator::RegExpStringIterator:
templateObject = inspector->getTemplateObjectForNative(
pc, js::intrinsic_NewRegExpStringIterator);
MOZ_ASSERT_IF(templateObject,
templateObject->is<RegExpStringIteratorObject>());
break;
}
if (!templateObject) {
return InliningStatus_NotInlined;
}
callInfo.setImplicitlyUsedUnchecked();
MConstant* templateConst =
MConstant::NewConstraintlessObject(alloc(), templateObject);
current->add(templateConst);
MNewIterator* ins =
MNewIterator::New(alloc(), constraints(), templateConst, type);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineArrayIteratorPrototypeOptimizable(
CallInfo& callInfo) {
MOZ_ASSERT(!callInfo.constructing());
MOZ_ASSERT(callInfo.argc() == 0);
if (!ensureArrayIteratorPrototypeNextNotModified()) {
return InliningStatus_NotInlined;
}
callInfo.setImplicitlyUsedUnchecked();
pushConstant(BooleanValue(true));
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathAbs(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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::InliningResult IonBuilder::inlineMathFloor(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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)) {
if (returnType == MIRType::Int32) {
callInfo.setImplicitlyUsedUnchecked();
MFloor* ins = MFloor::New(alloc(), callInfo.getArg(0));
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
if (returnType == MIRType::Double) {
callInfo.setImplicitlyUsedUnchecked();
MInstruction* ins = nullptr;
if (MNearbyInt::HasAssemblerSupport(RoundingMode::Down)) {
ins = MNearbyInt::New(alloc(), callInfo.getArg(0), argType,
RoundingMode::Down);
} else {
ins = MMathFunction::New(alloc(), callInfo.getArg(0),
UnaryMathFunction::Floor);
}
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
}
return InliningStatus_NotInlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathCeil(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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)) {
if (returnType == MIRType::Int32) {
callInfo.setImplicitlyUsedUnchecked();
MCeil* ins = MCeil::New(alloc(), callInfo.getArg(0));
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
if (returnType == MIRType::Double) {
callInfo.setImplicitlyUsedUnchecked();
MInstruction* ins = nullptr;
if (MNearbyInt::HasAssemblerSupport(RoundingMode::Up)) {
ins = MNearbyInt::New(alloc(), callInfo.getArg(0), argType,
RoundingMode::Up);
} else {
ins = MMathFunction::New(alloc(), callInfo.getArg(0),
UnaryMathFunction::Ceil);
}
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
}
return InliningStatus_NotInlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathClz32(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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::InliningResult IonBuilder::inlineMathRound(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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),
UnaryMathFunction::Round);
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
return InliningStatus_NotInlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathSqrt(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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::InliningResult IonBuilder::inlineMathAtan2(CallInfo& callInfo) {
if (callInfo.argc() != 2 || callInfo.constructing()) {
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::InliningResult IonBuilder::inlineMathHypot(CallInfo& callInfo) {
if (callInfo.constructing()) {
return InliningStatus_NotInlined;
}
uint32_t argc = callInfo.argc();
if (argc < 2 || argc > 4) {
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::InliningResult IonBuilder::inlineMathPow(CallInfo& callInfo) {
if (callInfo.argc() != 2 || callInfo.constructing()) {
return InliningStatus_NotInlined;
}
bool emitted = false;
MOZ_TRY(powTrySpecialized(&emitted, callInfo.getArg(0), callInfo.getArg(1),
getInlineReturnType()));
if (!emitted) {
return InliningStatus_NotInlined;
}
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathRandom(CallInfo& callInfo) {
if (callInfo.constructing()) {
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()->realm()->getOrCreateRandomNumberGenerator();
callInfo.setImplicitlyUsedUnchecked();
MRandom* rand = MRandom::New(alloc());
current->add(rand);
current->push(rand);
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathImul(CallInfo& callInfo) {
if (callInfo.argc() != 2 || callInfo.constructing()) {
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::InliningResult IonBuilder::inlineMathFRound(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
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::InliningResult IonBuilder::inlineMathTrunc(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
return InliningStatus_NotInlined;
}
MIRType argType = callInfo.getArg(0)->type();
MIRType returnType = getInlineReturnType();
// Math.trunc(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)) {
if (returnType == MIRType::Int32) {
callInfo.setImplicitlyUsedUnchecked();
MTrunc* ins = MTrunc::New(alloc(), callInfo.getArg(0));
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
if (returnType == MIRType::Double) {
callInfo.setImplicitlyUsedUnchecked();
MInstruction* ins = nullptr;
if (MNearbyInt::HasAssemblerSupport(RoundingMode::TowardsZero)) {
ins = MNearbyInt::New(alloc(), callInfo.getArg(0), argType,
RoundingMode::TowardsZero);
} else {
ins = MMathFunction::New(alloc(), callInfo.getArg(0),
UnaryMathFunction::Trunc);
}
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
}
return InliningStatus_NotInlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathSign(CallInfo& callInfo) {
if (callInfo.argc() != 1 || callInfo.constructing()) {
return InliningStatus_NotInlined;
}
MIRType argType = callInfo.getArg(0)->type();
MIRType returnType = getInlineReturnType();
if (returnType != MIRType::Int32 && returnType != MIRType::Double) {
return InliningStatus_NotInlined;
}
if (!IsFloatingPointType(argType) &&
!(argType == MIRType::Int32 && returnType == MIRType::Int32)) {
return InliningStatus_NotInlined;
}
callInfo.setImplicitlyUsedUnchecked();
auto* ins = MSign::New(alloc(), callInfo.getArg(0), returnType);
current->add(ins);
current->push(ins);
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineMathMinMax(CallInfo& callInfo,
bool max) {
if (callInfo.argc() < 1 || callInfo.constructing()) {
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 abort(AbortReason::Alloc);
}
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().fallible(), last, cases[i], returnType, max);
if (!ins) {
return abort(AbortReason::Alloc);
}
current->add(ins);
last = ins;
}
current->push(last);
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineStringObject(CallInfo& callInfo) {
if (callInfo.argc() != 1 || !callInfo.constructing()) {
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);
MOZ_TRY(resumeAfter(ins));
return InliningStatus_Inlined;
}
IonBuilder::InliningResult IonBuilder::inlineStringSplitString(
CallInfo& callInfo) {
MOZ_ASSERT(!callInfo.constructing());
MOZ_ASSERT(callInfo.argc() == 2);
MDefinition* strArg = callInfo.getArg(0);
MDefinition* sepArg = callInfo.getArg</