/* -*- 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 "jit/CodeGenerator.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/DebugOnly.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/ScopeExit.h" #include "mozilla/SizePrintfMacros.h" #include "jslibmath.h" #include "jsmath.h" #include "jsnum.h" #include "jsprf.h" #include "builtin/Eval.h" #include "builtin/TypedObject.h" #include "gc/Nursery.h" #include "irregexp/NativeRegExpMacroAssembler.h" #include "jit/AtomicOperations.h" #include "jit/BaselineCompiler.h" #include "jit/IonBuilder.h" #include "jit/IonCaches.h" #include "jit/IonOptimizationLevels.h" #include "jit/JitcodeMap.h" #include "jit/JitSpewer.h" #include "jit/Linker.h" #include "jit/Lowering.h" #include "jit/MIRGenerator.h" #include "jit/MoveEmitter.h" #include "jit/RangeAnalysis.h" #include "jit/SharedICHelpers.h" #include "vm/MatchPairs.h" #include "vm/RegExpStatics.h" #include "vm/TraceLogging.h" #include "vm/Unicode.h" #include "jsboolinlines.h" #include "jit/MacroAssembler-inl.h" #include "jit/shared/CodeGenerator-shared-inl.h" #include "jit/shared/Lowering-shared-inl.h" #include "vm/Interpreter-inl.h" using namespace js; using namespace js::jit; using mozilla::DebugOnly; using mozilla::FloatingPoint; using mozilla::Maybe; using mozilla::NegativeInfinity; using mozilla::PositiveInfinity; using JS::GenericNaN; namespace js { namespace jit { // This out-of-line cache is used to do a double dispatch including it-self and // the wrapped IonCache. class OutOfLineUpdateCache : public OutOfLineCodeBase, public IonCacheVisitor { private: LInstruction* lir_; size_t cacheIndex_; RepatchLabel entry_; public: OutOfLineUpdateCache(LInstruction* lir, size_t cacheIndex) : lir_(lir), cacheIndex_(cacheIndex) { } void bind(MacroAssembler* masm) { // The binding of the initial jump is done in // CodeGenerator::visitOutOfLineCache. } size_t getCacheIndex() const { return cacheIndex_; } LInstruction* lir() const { return lir_; } RepatchLabel& entry() { return entry_; } void accept(CodeGenerator* codegen) { codegen->visitOutOfLineCache(this); } // ICs' visit functions delegating the work to the CodeGen visit funtions. #define VISIT_CACHE_FUNCTION(op) \ void visit##op##IC(CodeGenerator* codegen) { \ CodeGenerator::DataPtr ic(codegen, getCacheIndex()); \ codegen->visit##op##IC(this, ic); \ } IONCACHE_KIND_LIST(VISIT_CACHE_FUNCTION) #undef VISIT_CACHE_FUNCTION }; // This function is declared here because it needs to instantiate an // OutOfLineUpdateCache, but we want to keep it visible inside the // CodeGeneratorShared such as we can specialize inline caches in function of // the architecture. void CodeGeneratorShared::addCache(LInstruction* lir, size_t cacheIndex) { if (cacheIndex == SIZE_MAX) { masm.setOOM(); return; } DataPtr cache(this, cacheIndex); MInstruction* mir = lir->mirRaw()->toInstruction(); if (mir->resumePoint()) cache->setScriptedLocation(mir->block()->info().script(), mir->resumePoint()->pc()); else cache->setIdempotent(); OutOfLineUpdateCache* ool = new(alloc()) OutOfLineUpdateCache(lir, cacheIndex); addOutOfLineCode(ool, mir); cache->emitInitialJump(masm, ool->entry()); masm.bind(ool->rejoin()); } void CodeGenerator::visitOutOfLineCache(OutOfLineUpdateCache* ool) { DataPtr cache(this, ool->getCacheIndex()); // Register the location of the OOL path in the IC. cache->setFallbackLabel(masm.labelForPatch()); masm.bind(&ool->entry()); // Dispatch to ICs' accept functions. cache->accept(this, ool); } StringObject* MNewStringObject::templateObj() const { return &templateObj_->as(); } CodeGenerator::CodeGenerator(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm) : CodeGeneratorSpecific(gen, graph, masm) , ionScriptLabels_(gen->alloc()) , scriptCounts_(nullptr) , simdRefreshTemplatesDuringLink_(0) { } CodeGenerator::~CodeGenerator() { MOZ_ASSERT_IF(!gen->compilingAsmJS(), masm.numAsmJSAbsoluteAddresses() == 0); js_delete(scriptCounts_); } typedef bool (*StringToNumberFn)(ExclusiveContext*, JSString*, double*); static const VMFunction StringToNumberInfo = FunctionInfo(StringToNumber); void CodeGenerator::visitValueToInt32(LValueToInt32* lir) { ValueOperand operand = ToValue(lir, LValueToInt32::Input); Register output = ToRegister(lir->output()); FloatRegister temp = ToFloatRegister(lir->tempFloat()); MDefinition* input; if (lir->mode() == LValueToInt32::NORMAL) input = lir->mirNormal()->input(); else input = lir->mirTruncate()->input(); Label fails; if (lir->mode() == LValueToInt32::TRUNCATE) { OutOfLineCode* oolDouble = oolTruncateDouble(temp, output, lir->mir()); // We can only handle strings in truncation contexts, like bitwise // operations. Label* stringEntry; Label* stringRejoin; Register stringReg; if (input->mightBeType(MIRType_String)) { stringReg = ToRegister(lir->temp()); OutOfLineCode* oolString = oolCallVM(StringToNumberInfo, lir, ArgList(stringReg), StoreFloatRegisterTo(temp)); stringEntry = oolString->entry(); stringRejoin = oolString->rejoin(); } else { stringReg = InvalidReg; stringEntry = nullptr; stringRejoin = nullptr; } masm.truncateValueToInt32(operand, input, stringEntry, stringRejoin, oolDouble->entry(), stringReg, temp, output, &fails); masm.bind(oolDouble->rejoin()); } else { masm.convertValueToInt32(operand, input, temp, output, &fails, lir->mirNormal()->canBeNegativeZero(), lir->mirNormal()->conversion()); } bailoutFrom(&fails, lir->snapshot()); } void CodeGenerator::visitValueToDouble(LValueToDouble* lir) { MToDouble* mir = lir->mir(); ValueOperand operand = ToValue(lir, LValueToDouble::Input); FloatRegister output = ToFloatRegister(lir->output()); Register tag = masm.splitTagForTest(operand); Label isDouble, isInt32, isBool, isNull, isUndefined, done; bool hasBoolean = false, hasNull = false, hasUndefined = false; masm.branchTestDouble(Assembler::Equal, tag, &isDouble); masm.branchTestInt32(Assembler::Equal, tag, &isInt32); if (mir->conversion() != MToFPInstruction::NumbersOnly) { masm.branchTestBoolean(Assembler::Equal, tag, &isBool); masm.branchTestUndefined(Assembler::Equal, tag, &isUndefined); hasBoolean = true; hasUndefined = true; if (mir->conversion() != MToFPInstruction::NonNullNonStringPrimitives) { masm.branchTestNull(Assembler::Equal, tag, &isNull); hasNull = true; } } bailout(lir->snapshot()); if (hasNull) { masm.bind(&isNull); masm.loadConstantDouble(0.0, output); masm.jump(&done); } if (hasUndefined) { masm.bind(&isUndefined); masm.loadConstantDouble(GenericNaN(), output); masm.jump(&done); } if (hasBoolean) { masm.bind(&isBool); masm.boolValueToDouble(operand, output); masm.jump(&done); } masm.bind(&isInt32); masm.int32ValueToDouble(operand, output); masm.jump(&done); masm.bind(&isDouble); masm.unboxDouble(operand, output); masm.bind(&done); } void CodeGenerator::visitValueToFloat32(LValueToFloat32* lir) { MToFloat32* mir = lir->mir(); ValueOperand operand = ToValue(lir, LValueToFloat32::Input); FloatRegister output = ToFloatRegister(lir->output()); Register tag = masm.splitTagForTest(operand); Label isDouble, isInt32, isBool, isNull, isUndefined, done; bool hasBoolean = false, hasNull = false, hasUndefined = false; masm.branchTestDouble(Assembler::Equal, tag, &isDouble); masm.branchTestInt32(Assembler::Equal, tag, &isInt32); if (mir->conversion() != MToFPInstruction::NumbersOnly) { masm.branchTestBoolean(Assembler::Equal, tag, &isBool); masm.branchTestUndefined(Assembler::Equal, tag, &isUndefined); hasBoolean = true; hasUndefined = true; if (mir->conversion() != MToFPInstruction::NonNullNonStringPrimitives) { masm.branchTestNull(Assembler::Equal, tag, &isNull); hasNull = true; } } bailout(lir->snapshot()); if (hasNull) { masm.bind(&isNull); masm.loadConstantFloat32(0.0f, output); masm.jump(&done); } if (hasUndefined) { masm.bind(&isUndefined); masm.loadConstantFloat32(float(GenericNaN()), output); masm.jump(&done); } if (hasBoolean) { masm.bind(&isBool); masm.boolValueToFloat32(operand, output); masm.jump(&done); } masm.bind(&isInt32); masm.int32ValueToFloat32(operand, output); masm.jump(&done); masm.bind(&isDouble); // ARM and MIPS may not have a double register available if we've // allocated output as a float32. #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) masm.unboxDouble(operand, ScratchDoubleReg); masm.convertDoubleToFloat32(ScratchDoubleReg, output); #else masm.unboxDouble(operand, output); masm.convertDoubleToFloat32(output, output); #endif masm.bind(&done); } void CodeGenerator::visitInt32ToDouble(LInt32ToDouble* lir) { masm.convertInt32ToDouble(ToRegister(lir->input()), ToFloatRegister(lir->output())); } void CodeGenerator::visitFloat32ToDouble(LFloat32ToDouble* lir) { masm.convertFloat32ToDouble(ToFloatRegister(lir->input()), ToFloatRegister(lir->output())); } void CodeGenerator::visitDoubleToFloat32(LDoubleToFloat32* lir) { masm.convertDoubleToFloat32(ToFloatRegister(lir->input()), ToFloatRegister(lir->output())); } void CodeGenerator::visitInt32ToFloat32(LInt32ToFloat32* lir) { masm.convertInt32ToFloat32(ToRegister(lir->input()), ToFloatRegister(lir->output())); } void CodeGenerator::visitDoubleToInt32(LDoubleToInt32* lir) { Label fail; FloatRegister input = ToFloatRegister(lir->input()); Register output = ToRegister(lir->output()); masm.convertDoubleToInt32(input, output, &fail, lir->mir()->canBeNegativeZero()); bailoutFrom(&fail, lir->snapshot()); } void CodeGenerator::visitFloat32ToInt32(LFloat32ToInt32* lir) { Label fail; FloatRegister input = ToFloatRegister(lir->input()); Register output = ToRegister(lir->output()); masm.convertFloat32ToInt32(input, output, &fail, lir->mir()->canBeNegativeZero()); bailoutFrom(&fail, lir->snapshot()); } void CodeGenerator::emitOOLTestObject(Register objreg, Label* ifEmulatesUndefined, Label* ifDoesntEmulateUndefined, Register scratch) { saveVolatile(scratch); masm.setupUnalignedABICall(scratch); masm.passABIArg(objreg); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::EmulatesUndefined)); masm.storeCallResult(scratch); restoreVolatile(scratch); masm.branchIfTrueBool(scratch, ifEmulatesUndefined); masm.jump(ifDoesntEmulateUndefined); } // Base out-of-line code generator for all tests of the truthiness of an // object, where the object might not be truthy. (Recall that per spec all // objects are truthy, but we implement the JSCLASS_EMULATES_UNDEFINED class // flag to permit objects to look like |undefined| in certain contexts, // including in object truthiness testing.) We check truthiness inline except // when we're testing it on a proxy (or if TI guarantees us that the specified // object will never emulate |undefined|), in which case out-of-line code will // call EmulatesUndefined for a conclusive answer. class OutOfLineTestObject : public OutOfLineCodeBase { Register objreg_; Register scratch_; Label* ifEmulatesUndefined_; Label* ifDoesntEmulateUndefined_; #ifdef DEBUG bool initialized() { return ifEmulatesUndefined_ != nullptr; } #endif public: OutOfLineTestObject() #ifdef DEBUG : ifEmulatesUndefined_(nullptr), ifDoesntEmulateUndefined_(nullptr) #endif { } void accept(CodeGenerator* codegen) final override { MOZ_ASSERT(initialized()); codegen->emitOOLTestObject(objreg_, ifEmulatesUndefined_, ifDoesntEmulateUndefined_, scratch_); } // Specify the register where the object to be tested is found, labels to // jump to if the object is truthy or falsy, and a scratch register for // use in the out-of-line path. void setInputAndTargets(Register objreg, Label* ifEmulatesUndefined, Label* ifDoesntEmulateUndefined, Register scratch) { MOZ_ASSERT(!initialized()); MOZ_ASSERT(ifEmulatesUndefined); objreg_ = objreg; scratch_ = scratch; ifEmulatesUndefined_ = ifEmulatesUndefined; ifDoesntEmulateUndefined_ = ifDoesntEmulateUndefined; } }; // A subclass of OutOfLineTestObject containing two extra labels, for use when // the ifTruthy/ifFalsy labels are needed in inline code as well as out-of-line // code. The user should bind these labels in inline code, and specify them as // targets via setInputAndTargets, as appropriate. class OutOfLineTestObjectWithLabels : public OutOfLineTestObject { Label label1_; Label label2_; public: OutOfLineTestObjectWithLabels() { } Label* label1() { return &label1_; } Label* label2() { return &label2_; } }; void CodeGenerator::testObjectEmulatesUndefinedKernel(Register objreg, Label* ifEmulatesUndefined, Label* ifDoesntEmulateUndefined, Register scratch, OutOfLineTestObject* ool) { ool->setInputAndTargets(objreg, ifEmulatesUndefined, ifDoesntEmulateUndefined, scratch); // Perform a fast-path check of the object's class flags if the object's // not a proxy. Let out-of-line code handle the slow cases that require // saving registers, making a function call, and restoring registers. masm.branchTestObjectTruthy(false, objreg, scratch, ool->entry(), ifEmulatesUndefined); } void CodeGenerator::branchTestObjectEmulatesUndefined(Register objreg, Label* ifEmulatesUndefined, Label* ifDoesntEmulateUndefined, Register scratch, OutOfLineTestObject* ool) { MOZ_ASSERT(!ifDoesntEmulateUndefined->bound(), "ifDoesntEmulateUndefined will be bound to the fallthrough path"); testObjectEmulatesUndefinedKernel(objreg, ifEmulatesUndefined, ifDoesntEmulateUndefined, scratch, ool); masm.bind(ifDoesntEmulateUndefined); } void CodeGenerator::testObjectEmulatesUndefined(Register objreg, Label* ifEmulatesUndefined, Label* ifDoesntEmulateUndefined, Register scratch, OutOfLineTestObject* ool) { testObjectEmulatesUndefinedKernel(objreg, ifEmulatesUndefined, ifDoesntEmulateUndefined, scratch, ool); masm.jump(ifDoesntEmulateUndefined); } void CodeGenerator::testValueTruthyKernel(const ValueOperand& value, const LDefinition* scratch1, const LDefinition* scratch2, FloatRegister fr, Label* ifTruthy, Label* ifFalsy, OutOfLineTestObject* ool, MDefinition* valueMIR) { // Count the number of possible type tags we might have, so we'll know when // we've checked them all and hence can avoid emitting a tag check for the // last one. In particular, whenever tagCount is 1 that means we've tried // all but one of them already so we know exactly what's left based on the // mightBe* booleans. bool mightBeUndefined = valueMIR->mightBeType(MIRType_Undefined); bool mightBeNull = valueMIR->mightBeType(MIRType_Null); bool mightBeBoolean = valueMIR->mightBeType(MIRType_Boolean); bool mightBeInt32 = valueMIR->mightBeType(MIRType_Int32); bool mightBeObject = valueMIR->mightBeType(MIRType_Object); bool mightBeString = valueMIR->mightBeType(MIRType_String); bool mightBeSymbol = valueMIR->mightBeType(MIRType_Symbol); bool mightBeDouble = valueMIR->mightBeType(MIRType_Double); int tagCount = int(mightBeUndefined) + int(mightBeNull) + int(mightBeBoolean) + int(mightBeInt32) + int(mightBeObject) + int(mightBeString) + int(mightBeSymbol) + int(mightBeDouble); MOZ_ASSERT_IF(!valueMIR->emptyResultTypeSet(), tagCount > 0); // If we know we're null or undefined, we're definitely falsy, no // need to even check the tag. if (int(mightBeNull) + int(mightBeUndefined) == tagCount) { masm.jump(ifFalsy); return; } Register tag = masm.splitTagForTest(value); if (mightBeUndefined) { MOZ_ASSERT(tagCount > 1); masm.branchTestUndefined(Assembler::Equal, tag, ifFalsy); --tagCount; } if (mightBeNull) { MOZ_ASSERT(tagCount > 1); masm.branchTestNull(Assembler::Equal, tag, ifFalsy); --tagCount; } if (mightBeBoolean) { MOZ_ASSERT(tagCount != 0); Label notBoolean; if (tagCount != 1) masm.branchTestBoolean(Assembler::NotEqual, tag, ¬Boolean); masm.branchTestBooleanTruthy(false, value, ifFalsy); if (tagCount != 1) masm.jump(ifTruthy); // Else just fall through to truthiness. masm.bind(¬Boolean); --tagCount; } if (mightBeInt32) { MOZ_ASSERT(tagCount != 0); Label notInt32; if (tagCount != 1) masm.branchTestInt32(Assembler::NotEqual, tag, ¬Int32); masm.branchTestInt32Truthy(false, value, ifFalsy); if (tagCount != 1) masm.jump(ifTruthy); // Else just fall through to truthiness. masm.bind(¬Int32); --tagCount; } if (mightBeObject) { MOZ_ASSERT(tagCount != 0); if (ool) { Label notObject; if (tagCount != 1) masm.branchTestObject(Assembler::NotEqual, tag, ¬Object); Register objreg = masm.extractObject(value, ToRegister(scratch1)); testObjectEmulatesUndefined(objreg, ifFalsy, ifTruthy, ToRegister(scratch2), ool); masm.bind(¬Object); } else { if (tagCount != 1) masm.branchTestObject(Assembler::Equal, tag, ifTruthy); // Else just fall through to truthiness. } --tagCount; } else { MOZ_ASSERT(!ool, "We better not have an unused OOL path, since the code generator will try to " "generate code for it but we never set up its labels, which will cause null " "derefs of those labels."); } if (mightBeString) { // Test if a string is non-empty. MOZ_ASSERT(tagCount != 0); Label notString; if (tagCount != 1) masm.branchTestString(Assembler::NotEqual, tag, ¬String); masm.branchTestStringTruthy(false, value, ifFalsy); if (tagCount != 1) masm.jump(ifTruthy); // Else just fall through to truthiness. masm.bind(¬String); --tagCount; } if (mightBeSymbol) { // All symbols are truthy. MOZ_ASSERT(tagCount != 0); if (tagCount != 1) masm.branchTestSymbol(Assembler::Equal, tag, ifTruthy); // Else fall through to ifTruthy. --tagCount; } if (mightBeDouble) { MOZ_ASSERT(tagCount == 1); // If we reach here the value is a double. masm.unboxDouble(value, fr); masm.branchTestDoubleTruthy(false, fr, ifFalsy); --tagCount; } MOZ_ASSERT(tagCount == 0); // Fall through for truthy. } void CodeGenerator::testValueTruthy(const ValueOperand& value, const LDefinition* scratch1, const LDefinition* scratch2, FloatRegister fr, Label* ifTruthy, Label* ifFalsy, OutOfLineTestObject* ool, MDefinition* valueMIR) { testValueTruthyKernel(value, scratch1, scratch2, fr, ifTruthy, ifFalsy, ool, valueMIR); masm.jump(ifTruthy); } Label* CodeGenerator::getJumpLabelForBranch(MBasicBlock* block) { // Skip past trivial blocks. block = skipTrivialBlocks(block); if (!labelForBackedgeWithImplicitCheck(block)) return block->lir()->label(); // We need to use a patchable jump for this backedge, but want to treat // this as a normal label target to simplify codegen. Efficiency isn't so // important here as these tests are extremely unlikely to be used in loop // backedges, so emit inline code for the patchable jump. Heap allocating // the label allows it to be used by out of line blocks. Label* res = alloc().lifoAlloc()->newInfallible