From 69698e03f04b2eca80304999ae2d04bc482d4569 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Thu, 18 Jul 2013 15:13:15 -0700 Subject: [PATCH] Bug 888109: Float32 general optimizations for IonMonkey: framework and arithmetic operations; r=sstangl,nbp --- js/public/Value.h | 8 + js/src/assembler/assembler/X86Assembler.h | 177 ++++++++++-- js/src/jit/BaselineIC.cpp | 10 +- js/src/jit/CodeGenerator.cpp | 95 +++++++ js/src/jit/CodeGenerator.h | 5 + js/src/jit/IonAnalysis.cpp | 265 +++++++++++++++++- js/src/jit/IonBuilder.cpp | 3 + js/src/jit/IonFrames.cpp | 16 ++ js/src/jit/IonMacroAssembler.cpp | 54 +++- js/src/jit/IonMacroAssembler.h | 30 +- js/src/jit/IonTypes.h | 18 +- js/src/jit/LICM.cpp | 2 +- js/src/jit/LIR-Common.h | 114 ++++++++ js/src/jit/LIR.h | 1 + js/src/jit/LOpcodes.h | 8 + js/src/jit/Lowering.cpp | 91 +++++- js/src/jit/Lowering.h | 1 + js/src/jit/MCallOptimize.cpp | 4 +- js/src/jit/MIR.cpp | 97 ++++++- js/src/jit/MIR.h | 139 ++++++++- js/src/jit/MOpcodes.h | 1 + js/src/jit/ParallelSafetyAnalysis.cpp | 1 + js/src/jit/RangeAnalysis.cpp | 28 ++ js/src/jit/SnapshotReader.h | 18 +- js/src/jit/SnapshotWriter.h | 2 + js/src/jit/Snapshots.cpp | 39 ++- js/src/jit/TypePolicy.cpp | 115 +++++++- js/src/jit/TypePolicy.h | 42 ++- js/src/jit/arm/Lowering-arm.h | 7 + js/src/jit/arm/MacroAssembler-arm.h | 35 +++ js/src/jit/shared/Assembler-x86-shared.h | 115 ++++++++ js/src/jit/shared/CodeGenerator-shared.cpp | 16 +- .../jit/shared/CodeGenerator-x86-shared.cpp | 46 +++ js/src/jit/shared/CodeGenerator-x86-shared.h | 3 + js/src/jit/shared/Lowering-shared-inl.h | 5 +- js/src/jit/shared/Lowering-shared.h | 5 + js/src/jit/shared/Lowering-x86-shared.cpp | 9 + js/src/jit/shared/Lowering-x86-shared.h | 1 + js/src/jit/shared/MacroAssembler-x86-shared.h | 56 ++++ js/src/jit/x64/Lowering-x64.cpp | 16 +- js/src/jit/x64/Lowering-x64.h | 4 + js/src/jit/x64/MacroAssembler-x64.h | 19 ++ js/src/jit/x86/Assembler-x86.cpp | 1 + js/src/jit/x86/Assembler-x86.h | 16 ++ js/src/jit/x86/CodeGenerator-x86.cpp | 10 +- js/src/jit/x86/Lowering-x86.cpp | 16 +- js/src/jit/x86/Lowering-x86.h | 4 + js/src/jit/x86/MacroAssembler-x86.cpp | 43 ++- js/src/jit/x86/MacroAssembler-x86.h | 36 +++ 49 files changed, 1747 insertions(+), 100 deletions(-) diff --git a/js/public/Value.h b/js/public/Value.h index 57ec128e9b2..dfd321a586f 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -1273,6 +1273,14 @@ DoubleValue(double dbl) return v; } +static inline Value +Float32Value(float f) +{ + Value v; + v.setDouble(f); + return v; +} + static inline Value StringValue(JSString *str) { diff --git a/js/src/assembler/assembler/X86Assembler.h b/js/src/assembler/assembler/X86Assembler.h index 538339ea81b..82229853cd4 100644 --- a/js/src/assembler/assembler/X86Assembler.h +++ b/js/src/assembler/assembler/X86Assembler.h @@ -2180,6 +2180,14 @@ public: m_formatter.twoByteOp(OP2_ADDSD_VsdWsd, (RegisterID)dst, (RegisterID)src); } + void addss_rr(XMMRegisterID src, XMMRegisterID dst) + { + spew("addss %s, %s", + nameFPReg(src), nameFPReg(dst)); + m_formatter.prefix(PRE_SSE_F3); + m_formatter.twoByteOp(OP2_ADDSD_VsdWsd, (RegisterID)dst, (RegisterID)src); + } + void addsd_mr(int offset, RegisterID base, XMMRegisterID dst) { spew("addsd %s0x%x(%s), %s", @@ -2188,6 +2196,14 @@ public: m_formatter.twoByteOp(OP2_ADDSD_VsdWsd, (RegisterID)dst, base, offset); } + void addss_mr(int offset, RegisterID base, XMMRegisterID dst) + { + spew("addss %s0x%x(%s), %s", + PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst)); + m_formatter.prefix(PRE_SSE_F3); + m_formatter.twoByteOp(OP2_ADDSD_VsdWsd, (RegisterID)dst, base, offset); + } + #if !WTF_CPU_X86_64 void addsd_mr(const void* address, XMMRegisterID dst) { @@ -2196,6 +2212,13 @@ public: m_formatter.prefix(PRE_SSE_F2); m_formatter.twoByteOp(OP2_ADDSD_VsdWsd, (RegisterID)dst, address); } + void addss_mr(const void* address, XMMRegisterID dst) + { + spew("addss %p, %s", + address, nameFPReg(dst)); + m_formatter.prefix(PRE_SSE_F3); + m_formatter.twoByteOp(OP2_ADDSD_VsdWsd, (RegisterID)dst, address); + } #endif void cvtss2sd_rr(XMMRegisterID src, XMMRegisterID dst) @@ -2214,6 +2237,14 @@ public: m_formatter.twoByteOp(OP2_CVTSD2SS_VsdEd, (RegisterID)dst, (RegisterID)src); } + void cvtsi2ss_rr(RegisterID src, XMMRegisterID dst) + { + spew("cvtsi2ss %s, %s", + nameIReg(src), nameFPReg(dst)); + m_formatter.prefix(PRE_SSE_F3); + m_formatter.twoByteOp(OP2_CVTSI2SD_VsdEd, (RegisterID)dst, src); + } + void cvtsi2sd_rr(RegisterID src, XMMRegisterID dst) { spew("cvtsi2sd %s, %s", @@ -2248,6 +2279,22 @@ public: m_formatter.twoByteOp(OP2_CVTSI2SD_VsdEd, (RegisterID)dst, base, index, scale, offset); } + void cvtsi2ss_mr(int offset, RegisterID base, XMMRegisterID dst) + { + spew("cvtsi2ss %s0x%x(%s), %s", + PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst)); + m_formatter.prefix(PRE_SSE_F3); + m_formatter.twoByteOp(OP2_CVTSI2SD_VsdEd, (RegisterID)dst, base, offset); + } + + void cvtsi2ss_mr(int offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst) + { + spew("cvtsi2ss %d(%s,%s,%d), %s", + offset, nameIReg(base), nameIReg(index), 1<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() != MToFloat32::NumbersOnly) { + masm.branchTestBoolean(Assembler::Equal, tag, &isBool); + masm.branchTestUndefined(Assembler::Equal, tag, &isUndefined); + hasBoolean = true; + hasUndefined = true; + if (mir->conversion() != MToFloat32::NonNullNonStringPrimitives) { + masm.branchTestNull(Assembler::Equal, tag, &isNull); + hasNull = true; + } + } + + if (!bailout(lir->snapshot())) + return false; + + if (hasNull) { + static float DoubleZeroFloat = DoubleZero; + masm.bind(&isNull); + masm.loadStaticFloat32(&DoubleZeroFloat, output); + masm.jump(&done); + } + + if (hasUndefined) { + static float js_NaN_float = js_NaN; + masm.bind(&isUndefined); + masm.loadStaticFloat32(&js_NaN_float, 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); + masm.unboxDouble(operand, output); + masm.convertDoubleToFloat(output, output); + masm.bind(&done); + + return true; +} + bool CodeGenerator::visitInt32ToDouble(LInt32ToDouble *lir) { @@ -309,6 +370,27 @@ CodeGenerator::visitInt32ToDouble(LInt32ToDouble *lir) return true; } +bool +CodeGenerator::visitFloat32ToDouble(LFloat32ToDouble *lir) +{ + masm.convertFloatToDouble(ToFloatRegister(lir->input()), ToFloatRegister(lir->output())); + return true; +} + +bool +CodeGenerator::visitDoubleToFloat32(LDoubleToFloat32 *lir) +{ + masm.convertDoubleToFloat(ToFloatRegister(lir->input()), ToFloatRegister(lir->output())); + return true; +} + +bool +CodeGenerator::visitInt32ToFloat32(LInt32ToFloat32 *lir) +{ + masm.convertInt32ToFloat32(ToRegister(lir->input()), ToFloatRegister(lir->output())); + return true; +} + bool CodeGenerator::visitDoubleToInt32(LDoubleToInt32 *lir) { @@ -7439,6 +7521,19 @@ CodeGenerator::visitAssertRangeD(LAssertRangeD *ins) return emitAssertRangeD(r, input, temp); } +bool +CodeGenerator::visitAssertRangeF(LAssertRangeF *ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister temp = ToFloatRegister(ins->temp()); + Range *r = ins->range(); + + masm.convertFloatToDouble(input, input); + bool success = emitAssertRangeD(r, input, temp); + masm.convertDoubleToFloat(input, input); + return success; +} + bool CodeGenerator::visitAssertRangeV(LAssertRangeV *ins) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 2de7040682b..72e9c5fa67e 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -71,6 +71,10 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitMoveGroup(LMoveGroup *group); bool visitValueToInt32(LValueToInt32 *lir); bool visitValueToDouble(LValueToDouble *lir); + bool visitValueToFloat32(LValueToFloat32 *lir); + bool visitFloat32ToDouble(LFloat32ToDouble *lir); + bool visitDoubleToFloat32(LDoubleToFloat32 *lir); + bool visitInt32ToFloat32(LInt32ToFloat32 *lir); bool visitInt32ToDouble(LInt32ToDouble *lir); void emitOOLTestObject(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch); bool visitTestOAndBranch(LTestOAndBranch *lir); @@ -305,6 +309,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitAssertRangeI(LAssertRangeI *ins); bool visitAssertRangeD(LAssertRangeD *ins); + bool visitAssertRangeF(LAssertRangeF *ins); bool visitAssertRangeV(LAssertRangeV *ins); IonScriptCounts *extractUnassociatedScriptCounts() { diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index f6511c49116..60338a23b4b 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -12,6 +12,7 @@ #include "jit/Ion.h" #include "jit/IonBuilder.h" #include "jit/LIR.h" +#include "jit/Lowering.h" #include "jit/MIRGraph.h" #include "jsinferinlines.h" @@ -400,6 +401,12 @@ class TypeAnalyzer bool adjustInputs(MDefinition *def); bool insertConversions(); + bool graphContainsFloat32(); + bool markPhiConsumers(); + bool markPhiProducers(); + bool specializeValidFloatOps(); + bool tryEmitFloatOperations(); + public: TypeAnalyzer(MIRGenerator *mir, MIRGraph &graph) : mir(mir), graph(graph) @@ -415,6 +422,7 @@ static MIRType GuessPhiType(MPhi *phi) { MIRType type = MIRType_None; + bool convertibleToFloat32 = false; for (size_t i = 0, e = phi->numOperands(); i < e; i++) { MDefinition *in = phi->getOperand(i); if (in->isPhi()) { @@ -429,6 +437,8 @@ GuessPhiType(MPhi *phi) } if (type == MIRType_None) { type = in->type(); + if (in->isConstant()) + convertibleToFloat32 = true; continue; } if (type != in->type()) { @@ -436,11 +446,20 @@ GuessPhiType(MPhi *phi) if (in->resultTypeSet() && in->resultTypeSet()->empty()) continue; - // Specialize phis with int32 and double operands as double. - if (IsNumberType(type) && IsNumberType(in->type())) + if (IsFloatType(type) && IsFloatType(in->type())) { + // Specialize phis with int32 and float32 operands as float32. + type = MIRType_Float32; + } else if (convertibleToFloat32 && in->type() == MIRType_Float32) { + // If we only saw constants before and encounter a Float32 value, promote previous + // constants to Float32 + type = MIRType_Float32; + } else if (IsNumberType(type) && IsNumberType(in->type())) { + // Specialize phis with int32 and double operands as double. type = MIRType_Double; - else + convertibleToFloat32 &= in->isConstant(); + } else { return MIRType_Value; + } } } return type; @@ -476,6 +495,13 @@ TypeAnalyzer::propagateSpecialization(MPhi *phi) continue; } if (use->type() != phi->type()) { + // Specialize phis with int32 and float operands as floats. + if (IsFloatType(use->type()) && IsFloatType(phi->type())) { + if (!respecialize(use, MIRType_Float32)) + return false; + continue; + } + // Specialize phis with int32 and double operands as double. if (IsNumberType(use->type()) && IsNumberType(phi->type())) { if (!respecialize(use, MIRType_Double)) @@ -546,9 +572,24 @@ TypeAnalyzer::adjustPhiInputs(MPhi *phi) } else { MInstruction *replacement; - if (phiType == MIRType_Double && in->type() == MIRType_Int32) { + if (phiType == MIRType_Double && IsFloatType(in->type())) { // Convert int32 operands to double. replacement = MToDouble::New(in); + } else if (phiType == MIRType_Float32) { + if (in->type() == MIRType_Int32 || in->type() == MIRType_Double) { + replacement = MToFloat32::New(in); + } else { + // See comment below + if (in->type() != MIRType_Value) { + MBox *box = MBox::New(in); + in->block()->insertBefore(in->block()->lastIns(), box); + in = box; + } + + MUnbox *unbox = MUnbox::New(in, MIRType_Double, MUnbox::Fallible); + in->block()->insertBefore(in->block()->lastIns(), unbox); + replacement = MToFloat32::New(in); + } } else { // If we know this branch will fail to convert to phiType, // insert a box that'll immediately fail in the fallible unbox @@ -583,8 +624,7 @@ TypeAnalyzer::adjustPhiInputs(MPhi *phi) // the original box. phi->replaceOperand(i, in->toUnbox()->input()); } else { - MBox *box = MBox::New(in); - in->block()->insertBefore(in->block()->lastIns(), box); + MDefinition *box = BoxInputsPolicy::alwaysBoxAt(in->block()->lastIns(), in); phi->replaceOperand(i, box); } } @@ -650,9 +690,222 @@ TypeAnalyzer::insertConversions() return true; } +// This function tries to emit Float32 specialized operations whenever it's possible. +// MIR nodes are flagged as: +// - Producers, when they can create Float32 that might need to be coerced into a Double. +// Loads in Float32 arrays and conversions to Float32 are producers. +// - Consumers, when they can have Float32 as inputs and validate a legal use of a Float32. +// Stores in Float32 arrays and conversions to Float32 are consumers. +// - Float32 commutative, when using the Float32 instruction instead of the Double instruction +// does not result in a compound loss of precision. This is the case for +, -, /, * with 2 +// operands, for instance. However, an addition with 3 operands is not commutative anymore, +// so an intermediate coercion is needed. +// Except for phis, all these flags are known after Ion building, so they cannot change during +// the process. +// +// The idea behind the algorithm is easy: whenever we can prove that a commutative operation +// has only producers as inputs and consumers as uses, we can specialize the operation as a +// float32 operation. Otherwise, we have to convert all float32 inputs to doubles. Even +// if a lot of conversions are produced, GVN will take care of eliminating the redundant ones. +// +// Phis have a special status. Phis need to be flagged as producers or consumers as they can +// be inputs or outputs of commutative instructions. Fortunately, producers and consumers +// properties are such that we can deduce the property using all non phis inputs first (which form +// an initial phi graph) and then propagate all properties from one phi to another using a +// fixed point algorithm. The algorithm is ensured to terminate as each iteration has less or as +// many flagged phis as the previous iteration (so the worst steady state case is all phis being +// flagged as false). +// +// In a nutshell, the algorithm applies three passes: +// 1 - Determine which phis are consumers. Each phi gets an initial value by making a global AND on +// all its non-phi inputs. Then each phi propagates its value to other phis. If after propagation, +// the flag value changed, we have to reapply the algorithm on all phi operands, as a phi is a +// consumer if all of its uses are consumers. +// 2 - Determine which phis are producers. It's the same algorithm, except that we have to reapply +// the algorithm on all phi uses, as a phi is a producer if all of its operands are producers. +// 3 - Go through all commutative operations and ensure their inputs are all producers and their +// uses are all consumers. +bool +TypeAnalyzer::markPhiConsumers() +{ + JS_ASSERT(phiWorklist_.empty()); + + // Iterate in postorder so worklist is initialized to RPO. + for (PostorderIterator block(graph.poBegin()); block != graph.poEnd(); ++block) { + if (mir->shouldCancel("Ensure Float32 commutativity - Consumer Phis - Initial state")) + return false; + + for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); ++phi) { + JS_ASSERT(!phi->isInWorklist()); + bool canConsumeFloat32 = true; + for (MUseDefIterator use(*phi); canConsumeFloat32 && use; use++) { + MDefinition *usedef = use.def(); + canConsumeFloat32 &= usedef->isPhi() || usedef->canConsumeFloat32(); + } + phi->setCanConsumeFloat32(canConsumeFloat32); + if (canConsumeFloat32 && !addPhiToWorklist(*phi)) + return false; + } + } + + while (!phiWorklist_.empty()) { + if (mir->shouldCancel("Ensure Float32 commutativity - Consumer Phis - Fixed point")) + return false; + + MPhi *phi = popPhi(); + JS_ASSERT(phi->canConsumeFloat32()); + + bool validConsumer = true; + for (MUseDefIterator use(phi); use; use++) { + MDefinition *def = use.def(); + if (def->isPhi() && !def->canConsumeFloat32()) { + validConsumer = false; + break; + } + } + + if (validConsumer) + continue; + + // Propagate invalidated phis + phi->setCanConsumeFloat32(false); + for (size_t i = 0, e = phi->numOperands(); i < e; ++i) { + MDefinition *input = phi->getOperand(i); + if (input->isPhi() && !input->isInWorklist() && input->canConsumeFloat32()) + { + if (!addPhiToWorklist(input->toPhi())) + return false; + } + } + } + return true; +} + +bool +TypeAnalyzer::markPhiProducers() +{ + JS_ASSERT(phiWorklist_.empty()); + + // Iterate in reverse postorder so worklist is initialized to PO. + for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { + if (mir->shouldCancel("Ensure Float32 commutativity - Producer Phis - initial state")) + return false; + + for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); ++phi) { + JS_ASSERT(!phi->isInWorklist()); + bool canProduceFloat32 = true; + for (size_t i = 0, e = phi->numOperands(); canProduceFloat32 && i < e; ++i) { + MDefinition *input = phi->getOperand(i); + canProduceFloat32 &= input->isPhi() || input->canProduceFloat32(); + } + phi->setCanProduceFloat32(canProduceFloat32); + if (canProduceFloat32 && !addPhiToWorklist(*phi)) + return false; + } + } + + while (!phiWorklist_.empty()) { + if (mir->shouldCancel("Ensure Float32 commutativity - Producer Phis - Fixed point")) + return false; + + MPhi *phi = popPhi(); + JS_ASSERT(phi->canProduceFloat32()); + + bool validProducer = true; + for (size_t i = 0, e = phi->numOperands(); i < e; ++i) { + MDefinition *input = phi->getOperand(i); + if (input->isPhi() && !input->canProduceFloat32()) { + validProducer = false; + break; + } + } + + if (validProducer) + continue; + + // Propagate invalidated phis + phi->setCanProduceFloat32(false); + for (MUseDefIterator use(phi); use; use++) { + MDefinition *def = use.def(); + if (def->isPhi() && !def->isInWorklist() && def->canProduceFloat32()) + { + if (!addPhiToWorklist(def->toPhi())) + return false; + } + } + } + return true; +} + +bool +TypeAnalyzer::specializeValidFloatOps() +{ + for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { + if (mir->shouldCancel("Ensure Float32 commutativity - Instructions")) + return false; + + for (MInstructionIterator ins(block->begin()); ins != block->end(); ++ins) { + if (!ins->isFloat32Commutative()) + continue; + + if (ins->type() == MIRType_Float32) + continue; + + // This call will try to specialize the instruction iff all uses are consumers and + // all inputs are producers. + ins->trySpecializeFloat32(); + } + } + return true; +} + +bool +TypeAnalyzer::graphContainsFloat32() +{ + for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { + if (mir->shouldCancel("Ensure Float32 commutativity - Graph contains Float32")) + return false; + + for (MDefinitionIterator def(*block); def; def++) { + if (def->type() == MIRType_Float32) + return true; + } + } + return false; +} + +bool +TypeAnalyzer::tryEmitFloatOperations() +{ + // Backends that currently don't know how to generate Float32 specialized instructions + // shouldn't run this pass and just let all instructions as specialized for Double. + if (!LIRGenerator::allowFloat32Optimizations()) + return true; + + // Asm.js uses the ahead of time type checks to specialize operations, no need to check + // them again at this point. + if (mir->compilingAsmJS()) + return true; + + // Check ahead of time that there is at least one definition typed as Float32, otherwise we + // don't need this pass. + if (!graphContainsFloat32()) + return true; + + if (!markPhiConsumers()) + return false; + if (!markPhiProducers()) + return false; + if (!specializeValidFloatOps()) + return false; + return true; +} + bool TypeAnalyzer::analyze() { + if (!tryEmitFloatOperations()) + return false; if (!specializePhis()) return false; if (!insertConversions()) diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index bee422e9789..1af6b44ddf9 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -6305,6 +6305,7 @@ jit::TypeSetIncludes(types::TypeSet *types, MIRType input, types::TypeSet *input case MIRType_Boolean: case MIRType_Int32: case MIRType_Double: + case MIRType_Float32: case MIRType_String: case MIRType_Magic: return types->hasType(types::Type::PrimitiveType(ValueTypeFromMIRType(input))); @@ -7023,6 +7024,8 @@ IonBuilder::jsop_getelem_typed(MDefinition *obj, MDefinition *index, knownType = allowDouble ? MIRType_Double : MIRType_Int32; break; case ScalarTypeRepresentation::TYPE_FLOAT32: + knownType = (LIRGenerator::allowFloat32Optimizations()) ? MIRType_Float32 : MIRType_Double; + break; case ScalarTypeRepresentation::TYPE_FLOAT64: knownType = MIRType_Double; break; diff --git a/js/src/jit/IonFrames.cpp b/js/src/jit/IonFrames.cpp index 178c09c1a00..06810849858 100644 --- a/js/src/jit/IonFrames.cpp +++ b/js/src/jit/IonFrames.cpp @@ -1241,6 +1241,22 @@ SnapshotIterator::slotValue(const Slot &slot) case SnapshotReader::DOUBLE_REG: return DoubleValue(machine_.read(slot.floatReg())); + case SnapshotReader::FLOAT32_REG: + { + double asDouble = machine_.read(slot.floatReg()); + // The register contains the encoding of a float32. We just read + // the bits without making any conversion. + float asFloat = *(float*) &asDouble; + return DoubleValue(asFloat); + } + + case SnapshotReader::FLOAT32_STACK: + { + double asDouble = ReadFrameDoubleSlot(fp_, slot.stackSlot()); + float asFloat = *(float*) &asDouble; // no conversion, see comment above. + return DoubleValue(asFloat); + } + case SnapshotReader::TYPED_REG: return FromTypedPayload(slot.knownType(), machine_.read(slot.reg())); diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index 898d9d1759a..bcce43e24a9 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -15,6 +15,7 @@ #include "jit/BaselineIC.h" #include "jit/BaselineJIT.h" #include "jit/BaselineRegisters.h" +#include "jit/Lowering.h" #include "jit/MIR.h" #include "vm/ForkJoin.h" @@ -304,6 +305,41 @@ MacroAssembler::moveNurseryPtr(const ImmMaybeNurseryPtr &ptr, const Register &re movePtr(ptr, reg); } +template +static void +StoreToTypedFloatArray(MacroAssembler &masm, int arrayType, const S &value, const T &dest) { + switch (arrayType) { + case ScalarTypeRepresentation::TYPE_FLOAT32: + if (LIRGenerator::allowFloat32Optimizations()) { + masm.storeFloat(value, dest); + } else { +#ifdef JS_MORE_DETERMINISTIC + // See the comment in ToDoubleForTypedArray. + masm.canonicalizeDouble(value); +#endif + masm.convertDoubleToFloat(value, ScratchFloatReg); + masm.storeFloat(ScratchFloatReg, dest); + } + break; + case ScalarTypeRepresentation::TYPE_FLOAT64: +#ifdef JS_MORE_DETERMINISTIC + // See the comment in ToDoubleForTypedArray. + masm.canonicalizeDouble(value); +#endif + masm.storeDouble(value, dest); + break; + default: + MOZ_ASSUME_UNREACHABLE("Invalid typed array type"); + } +} + +void MacroAssembler::storeToTypedFloatArray(int arrayType, const FloatRegister &value, const BaseIndex &dest) { + StoreToTypedFloatArray(*this, arrayType, value, dest); +} +void MacroAssembler::storeToTypedFloatArray(int arrayType, const FloatRegister &value, const Address &dest) { + StoreToTypedFloatArray(*this, arrayType, value, dest); +} + template void MacroAssembler::loadFromTypedArray(int arrayType, const T &src, AnyRegister dest, Register temp, @@ -337,11 +373,16 @@ MacroAssembler::loadFromTypedArray(int arrayType, const T &src, AnyRegister dest } break; case ScalarTypeRepresentation::TYPE_FLOAT32: - case ScalarTypeRepresentation::TYPE_FLOAT64: - if (arrayType == ScalarTypeRepresentation::TYPE_FLOAT32) + if (LIRGenerator::allowFloat32Optimizations()) { + loadFloat(src, dest.fpu()); + canonicalizeFloat(dest.fpu()); + } else { loadFloatAsDouble(src, dest.fpu()); - else - loadDouble(src, dest.fpu()); + canonicalizeDouble(dest.fpu()); + } + break; + case ScalarTypeRepresentation::TYPE_FLOAT64: + loadDouble(src, dest.fpu()); canonicalizeDouble(dest.fpu()); break; default: @@ -395,6 +436,11 @@ MacroAssembler::loadFromTypedArray(int arrayType, const T &src, const ValueOpera } break; case ScalarTypeRepresentation::TYPE_FLOAT32: + loadFromTypedArray(arrayType, src, AnyRegister(ScratchFloatReg), dest.scratchReg(), NULL); + if (LIRGenerator::allowFloat32Optimizations()) + convertFloatToDouble(ScratchFloatReg, ScratchFloatReg); + boxDouble(ScratchFloatReg, dest); + break; case ScalarTypeRepresentation::TYPE_FLOAT64: loadFromTypedArray(arrayType, src, AnyRegister(ScratchFloatReg), dest.scratchReg(), NULL); boxDouble(ScratchFloatReg, dest); diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index 22fa34978d8..857ff2aff17 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -430,7 +430,7 @@ class MacroAssembler : public MacroAssemblerSpecific void Push(TypedOrValueRegister v) { if (v.hasValue()) Push(v.valueReg()); - else if (v.type() == MIRType_Double) + else if (IsFloatingPointType(v.type())) Push(v.typedReg().fpu()); else Push(ValueTypeFromMIRType(v.type()), v.typedReg().gpr()); @@ -561,6 +561,14 @@ class MacroAssembler : public MacroAssemblerSpecific bind(¬NaN); } + void canonicalizeFloat(FloatRegister reg) { + static float js_NaN_float = js_NaN; + Label notNaN; + branchFloat(DoubleOrdered, reg, reg, ¬NaN); + loadStaticFloat32(&js_NaN_float, reg); + bind(¬NaN); + } + template void loadFromTypedArray(int arrayType, const T &src, AnyRegister dest, Register temp, Label *fail); @@ -589,24 +597,8 @@ class MacroAssembler : public MacroAssemblerSpecific } } - template - void storeToTypedFloatArray(int arrayType, FloatRegister value, const T &dest) { -#ifdef JS_MORE_DETERMINISTIC - // See the comment in ToDoubleForTypedArray. - canonicalizeDouble(value); -#endif - switch (arrayType) { - case ScalarTypeRepresentation::TYPE_FLOAT32: - convertDoubleToFloat(value, ScratchFloatReg); - storeFloat(ScratchFloatReg, dest); - break; - case ScalarTypeRepresentation::TYPE_FLOAT64: - storeDouble(value, dest); - break; - default: - MOZ_ASSUME_UNREACHABLE("Invalid typed array type"); - } - } + void storeToTypedFloatArray(int arrayType, const FloatRegister &value, const BaseIndex &dest); + void storeToTypedFloatArray(int arrayType, const FloatRegister &value, const Address &dest); Register extractString(const Address &address, Register scratch) { return extractObject(address, scratch); diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index cb5f4247bb5..99dd94c9dcb 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -83,6 +83,7 @@ enum MIRType MIRType_Boolean, MIRType_Int32, MIRType_Double, + MIRType_Float32, MIRType_String, MIRType_Object, MIRType_Magic, @@ -134,6 +135,7 @@ ValueTypeFromMIRType(MIRType type) return JSVAL_TYPE_BOOLEAN; case MIRType_Int32: return JSVAL_TYPE_INT32; + case MIRType_Float32: // Fall through, there's no JSVAL for Float32 case MIRType_Double: return JSVAL_TYPE_DOUBLE; case MIRType_String: @@ -166,6 +168,8 @@ StringFromMIRType(MIRType type) return "Int32"; case MIRType_Double: return "Double"; + case MIRType_Float32: + return "Float32"; case MIRType_String: return "String"; case MIRType_Object: @@ -192,7 +196,19 @@ StringFromMIRType(MIRType type) static inline bool IsNumberType(MIRType type) { - return type == MIRType_Int32 || type == MIRType_Double; + return type == MIRType_Int32 || type == MIRType_Double || type == MIRType_Float32; +} + +static inline bool +IsFloatType(MIRType type) +{ + return type == MIRType_Int32 || type == MIRType_Float32; +} + +static inline bool +IsFloatingPointType(MIRType type) +{ + return type == MIRType_Double || type == MIRType_Float32; } static inline bool diff --git a/js/src/jit/LICM.cpp b/js/src/jit/LICM.cpp index 813c923a2aa..49458baa121 100644 --- a/js/src/jit/LICM.cpp +++ b/js/src/jit/LICM.cpp @@ -248,7 +248,7 @@ Loop::requiresHoistedUse(const MDefinition *ins) const // hoisting on their own, in general. Floating-point constants typically // are worth hoisting, unless they'll end up being spilled (eg. due to a // call). - if (ins->isConstant() && (ins->type() != MIRType_Double || containsPossibleCall_)) + if (ins->isConstant() && (IsFloatingPointType(ins->type()) || containsPossibleCall_)) return true; return false; diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index ac1f1328427..b39fc390e8d 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -188,6 +188,22 @@ class LDouble : public LInstructionHelper<1, 0, 0> } }; +// Constant float32. +class LFloat32 : public LInstructionHelper<1, 0, 0> +{ + float f_; + public: + LIR_HEADER(Float32); + + LFloat32(float f) + : f_(f) + { } + + float getFloat() const { + return f_; + } +}; + // A constant Value. class LValue : public LInstructionHelper { @@ -2102,6 +2118,16 @@ class LNegD : public LInstructionHelper<1, 1, 0> } }; +// Negative of a float32. +class LNegF : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(NegF) + LNegF(const LAllocation &num) { + setOperand(0, num); + } +}; + // Absolute value of an integer. class LAbsI : public LInstructionHelper<1, 1, 0> { @@ -2302,6 +2328,23 @@ class LMathD : public LBinaryMath<0> } }; +// Performs an add, sub, mul, or div on two double values. +class LMathF: public LBinaryMath<0> +{ + JSOp jsop_; + + public: + LIR_HEADER(MathF) + + LMathF(JSOp jsop) + : jsop_(jsop) + { } + + JSOp jsop() const { + return jsop_; + } +}; + class LModD : public LBinaryMath<1> { public: @@ -2458,6 +2501,39 @@ class LInt32ToDouble : public LInstructionHelper<1, 1, 0> } }; +// Convert a 32-bit float to a double. +class LFloat32ToDouble : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(Float32ToDouble) + + LFloat32ToDouble(const LAllocation &input) { + setOperand(0, input); + } +}; + +// Convert a double to a 32-bit float. +class LDoubleToFloat32 : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(DoubleToFloat32) + + LDoubleToFloat32(const LAllocation &input) { + setOperand(0, input); + } +}; + +// Convert a 32-bit integer to a float32. +class LInt32ToFloat32 : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(Int32ToFloat32) + + LInt32ToFloat32(const LAllocation &input) { + setOperand(0, input); + } +}; + // Convert a value to a double. class LValueToDouble : public LInstructionHelper<1, BOX_PIECES, 0> { @@ -2470,6 +2546,18 @@ class LValueToDouble : public LInstructionHelper<1, BOX_PIECES, 0> } }; +// Convert a value to a float32. +class LValueToFloat32 : public LInstructionHelper<1, BOX_PIECES, 0> +{ + public: + LIR_HEADER(ValueToFloat32) + static const size_t Input = 0; + + MToFloat32 *mir() { + return mir_->toToFloat32(); + } +}; + // Convert a value to an int32. // Input: components of a Value // Output: 32-bit integer @@ -5004,6 +5092,32 @@ class LAssertRangeD : public LInstructionHelper<0, 1, 1> } }; +class LAssertRangeF : public LInstructionHelper<0, 1, 1> +{ + public: + LIR_HEADER(AssertRangeF) + + LAssertRangeF(const LAllocation &input, const LDefinition &temp) { + setOperand(0, input); + setTemp(0, temp); + } + + const LAllocation *input() { + return getOperand(0); + } + + const LDefinition *temp() { + return getTemp(0); + } + + MAssertRange *mir() { + return mir_->toAssertRange(); + } + Range *range() { + return mir()->range(); + } +}; + class LAssertRangeV : public LInstructionHelper<0, BOX_PIECES, 3> { public: diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h index 2bf19547c21..7e7d36d9440 100644 --- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -540,6 +540,7 @@ class LDefinition case MIRType_Object: return LDefinition::OBJECT; case MIRType_Double: + case MIRType_Float32: return LDefinition::DOUBLE; #if defined(JS_PUNBOX64) case MIRType_Value: diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index 88301cc5e41..a9b452f9511 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -15,6 +15,7 @@ _(Integer) \ _(Pointer) \ _(Double) \ + _(Float32) \ _(Value) \ _(Parameter) \ _(Callee) \ @@ -92,6 +93,7 @@ _(MinMaxD) \ _(NegI) \ _(NegD) \ + _(NegF) \ _(AbsI) \ _(AbsD) \ _(SqrtD) \ @@ -108,6 +110,7 @@ _(SubI) \ _(MulI) \ _(MathD) \ + _(MathF) \ _(ModD) \ _(BinaryV) \ _(Concat) \ @@ -115,8 +118,12 @@ _(CharCodeAt) \ _(FromCharCode) \ _(Int32ToDouble) \ + _(Float32ToDouble) \ + _(DoubleToFloat32) \ + _(Int32ToFloat32) \ _(ValueToDouble) \ _(ValueToInt32) \ + _(ValueToFloat32) \ _(DoubleToInt32) \ _(TruncateDToInt32) \ _(IntToString) \ @@ -249,6 +256,7 @@ _(CheckInterruptPar) \ _(AssertRangeI) \ _(AssertRangeD) \ + _(AssertRangeF) \ _(AssertRangeV) #if defined(JS_CPU_X86) diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 20dbcfcd0ee..6ff369e8cd9 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -1241,6 +1241,12 @@ LIRGenerator::visitAdd(MAdd *ins) return lowerForFPU(new LMathD(JSOP_ADD), ins, lhs, rhs); } + if (ins->specialization() == MIRType_Float32) { + JS_ASSERT(lhs->type() == MIRType_Float32); + ReorderCommutative(&lhs, &rhs); + return lowerForFPU(new LMathF(JSOP_ADD), ins, lhs, rhs); + } + return lowerBinaryV(JSOP_ADD, ins); } @@ -1269,6 +1275,10 @@ LIRGenerator::visitSub(MSub *ins) JS_ASSERT(lhs->type() == MIRType_Double); return lowerForFPU(new LMathD(JSOP_SUB), ins, lhs, rhs); } + if (ins->specialization() == MIRType_Float32) { + JS_ASSERT(lhs->type() == MIRType_Float32); + return lowerForFPU(new LMathF(JSOP_SUB), ins, lhs, rhs); + } return lowerBinaryV(JSOP_SUB, ins); } @@ -1295,6 +1305,18 @@ LIRGenerator::visitMul(MMul *ins) return lowerForFPU(new LMathD(JSOP_MUL), ins, lhs, rhs); } + if (ins->specialization() == MIRType_Float32) { + JS_ASSERT(lhs->type() == MIRType_Float32); + ReorderCommutative(&lhs, &rhs); + + // We apply the same optimizations as for doubles + if (lhs->isConstant() && lhs->toConstant()->value() == JS::Float32Value(-1.0f)) + return defineReuseInput(new LNegF(useRegisterAtStart(rhs)), ins, 0); + if (rhs->isConstant() && rhs->toConstant()->value() == JS::Float32Value(-1.0f)) + return defineReuseInput(new LNegF(useRegisterAtStart(lhs)), ins, 0); + + return lowerForFPU(new LMathF(JSOP_MUL), ins, lhs, rhs); + } return lowerBinaryV(JSOP_MUL, ins); } @@ -1314,6 +1336,10 @@ LIRGenerator::visitDiv(MDiv *ins) JS_ASSERT(lhs->type() == MIRType_Double); return lowerForFPU(new LMathD(JSOP_DIV), ins, lhs, rhs); } + if (ins->specialization() == MIRType_Float32) { + JS_ASSERT(lhs->type() == MIRType_Float32); + return lowerForFPU(new LMathF(JSOP_DIV), ins, lhs, rhs); + } return lowerBinaryV(JSOP_DIV, ins); } @@ -1506,6 +1532,12 @@ LIRGenerator::visitToDouble(MToDouble *convert) return define(lir, convert); } + case MIRType_Float32: + { + LFloat32ToDouble *lir = new LFloat32ToDouble(useRegister(opd)); + return define(lir, convert); + } + case MIRType_Double: return redefine(convert, opd); @@ -1516,6 +1548,56 @@ LIRGenerator::visitToDouble(MToDouble *convert) } } +bool +LIRGenerator::visitToFloat32(MToFloat32 *convert) +{ + MDefinition *opd = convert->input(); + mozilla::DebugOnly conversion = convert->conversion(); + + switch (opd->type()) { + case MIRType_Value: + { + LValueToFloat32 *lir = new LValueToFloat32(); + if (!useBox(lir, LValueToFloat32::Input, opd)) + return false; + return assignSnapshot(lir) && define(lir, convert); + } + + case MIRType_Null: + JS_ASSERT(conversion != MToFloat32::NonStringPrimitives); + return lowerConstantFloat32(0, convert); + + case MIRType_Undefined: + JS_ASSERT(conversion != MToFloat32::NumbersOnly); + return lowerConstantFloat32(js_NaN, convert); + + case MIRType_Boolean: + JS_ASSERT(conversion != MToFloat32::NumbersOnly); + /* FALLTHROUGH */ + + case MIRType_Int32: + { + LInt32ToFloat32 *lir = new LInt32ToFloat32(useRegister(opd)); + return define(lir, convert); + } + + case MIRType_Double: + { + LDoubleToFloat32 *lir = new LDoubleToFloat32(useRegister(opd)); + return define(lir, convert); + } + + case MIRType_Float32: + return redefine(convert, opd); + + default: + // Objects might be effectful. + // Strings are complicated - we don't handle them yet. + MOZ_ASSUME_UNREACHABLE("unexpected type"); + return false; + } +} + bool LIRGenerator::visitToInt32(MToInt32 *convert) { @@ -1822,6 +1904,9 @@ LIRGenerator::visitStoreSlot(MStoreSlot *ins) case MIRType_Double: return add(new LStoreSlotT(useRegister(ins->slots()), useRegister(ins->value())), ins); + case MIRType_Float32: + MOZ_ASSUME_UNREACHABLE("Float32 shouldn't be stored in a slot."); + default: return add(new LStoreSlotT(useRegister(ins->slots()), useRegisterOrConstant(ins->value())), ins); @@ -2252,7 +2337,7 @@ LIRGenerator::visitLoadTypedArrayElement(MLoadTypedArrayElement *ins) // We need a temp register for Uint32Array with known double result. LDefinition tempDef = LDefinition::BogusTemp(); - if (ins->arrayType() == ScalarTypeRepresentation::TYPE_UINT32 && ins->type() == MIRType_Double) + if (ins->arrayType() == ScalarTypeRepresentation::TYPE_UINT32 && IsFloatingPointType(ins->type())) tempDef = temp(); LLoadTypedArrayElement *lir = new LLoadTypedArrayElement(elements, index, tempDef); @@ -2500,6 +2585,10 @@ LIRGenerator::visitAssertRange(MAssertRange *ins) lir = new LAssertRangeD(useRegister(input), tempFloat()); break; + case MIRType_Float32: + lir = new LAssertRangeF(useRegister(input), tempFloat()); + break; + case MIRType_Value: lir = new LAssertRangeV(tempToUnbox(), tempFloat(), tempFloat()); if (!useBox(lir, LAssertRangeV::Input, input)) diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index e54362606c9..ec0eea8b2c6 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -153,6 +153,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitOsrValue(MOsrValue *value); bool visitOsrScopeChain(MOsrScopeChain *object); bool visitToDouble(MToDouble *convert); + bool visitToFloat32(MToFloat32 *convert); bool visitToInt32(MToInt32 *convert); bool visitTruncateToInt32(MTruncateToInt32 *truncate); bool visitToString(MToString *convert); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 2584553d464..870187e88b7 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -792,9 +792,9 @@ IonBuilder::inlineMathImul(CallInfo &callInfo) if (returnType != MIRType_Int32) return InliningStatus_NotInlined; - if (!IsNumberType(callInfo.getArg(0)->type())) + if (!IsNumberType(callInfo.getArg(0)->type()) || callInfo.getArg(0)->type() == MIRType_Float32) return InliningStatus_NotInlined; - if (!IsNumberType(callInfo.getArg(1)->type())) + if (!IsNumberType(callInfo.getArg(1)->type()) || callInfo.getArg(1)->type() == MIRType_Float32) return InliningStatus_NotInlined; callInfo.unwrapArgs(); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 439f451ca9f..0f305305416 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -452,6 +452,12 @@ MConstant::printOpcode(FILE *fp) const case MIRType_Double: fprintf(fp, "%f", value().toDouble()); break; + case MIRType_Float32: + { + float val = value().toDouble(); + fprintf(fp, "%f", val); + break; + } case MIRType_Object: if (value().toObject().is()) { JSFunction *fun = &value().toObject().as(); @@ -483,6 +489,28 @@ MConstant::printOpcode(FILE *fp) const } } +// Needs a static function to avoid overzealous optimizations by certain compilers (MSVC). +static bool +IsFloat32Representable(double x) +{ + float asFloat = static_cast(x); + double floatAsDouble = static_cast(asFloat); + return floatAsDouble == x; +} + +bool +MConstant::canProduceFloat32() const +{ + if (!IsNumberType(type())) + return false; + + if (type() == MIRType_Int32) + return IsFloat32Representable(static_cast(value_.toInt32())); + if (type() == MIRType_Double) + return IsFloat32Representable(value_.toDouble()); + return true; +} + void MControlInstruction::printOpcode(FILE *fp) const { @@ -746,7 +774,9 @@ jit::MergeTypes(MIRType *ptype, types::StackTypeSet **ptypeSet, if (newTypeSet && newTypeSet->empty()) return; if (newType != *ptype) { - if (IsNumberType(newType) && IsNumberType(*ptype)) { + if (IsFloatType(newType) && IsFloatType(*ptype)) { + *ptype = MIRType_Float32; + } else if (IsNumberType(newType) && IsNumberType(*ptype)) { *ptype = MIRType_Double; } else if (*ptype != MIRType_Value) { if (!*ptypeSet) @@ -1170,6 +1200,43 @@ MBinaryArithInstruction::foldsTo(bool useValueNumbers) return this; } +template static void +ConvertDefinitionToDouble(MDefinition *def, MInstruction *consumer) +{ + MInstruction *replace = MToDouble::New(def); + consumer->replaceOperand(Op, replace); + consumer->block()->insertBefore(consumer, replace); +} + +static bool +CheckUsesAreFloat32Consumers(MInstruction *ins) +{ + bool allConsumerUses = true; + for (MUseDefIterator use(ins); allConsumerUses && use; use++) + allConsumerUses &= use.def()->canConsumeFloat32(); + return allConsumerUses; +} + +void +MBinaryArithInstruction::trySpecializeFloat32() +{ + MDefinition *left = lhs(); + MDefinition *right = rhs(); + + if (!left->canProduceFloat32() || !right->canProduceFloat32() + || !CheckUsesAreFloat32Consumers(this)) + { + if (left->type() == MIRType_Float32) + ConvertDefinitionToDouble<0>(left, this); + if (right->type() == MIRType_Float32) + ConvertDefinitionToDouble<1>(right, this); + return; + } + + specialization_ = MIRType_Float32; + setResultType(MIRType_Float32); +} + bool MAbs::fallible() const { @@ -1408,7 +1475,9 @@ MBinaryArithInstruction::infer(BaselineInspector *inspector, // Don't specialize for neither-integer-nor-double results. if (lhs == MIRType_Int32 && rhs == MIRType_Int32) setResultType(MIRType_Int32); - else if (lhs == MIRType_Double || rhs == MIRType_Double) + // Double operations are prioritary over float32 operations (i.e. if any operand needs + // a double as an input, convert all operands to doubles) + else if (IsFloatingPointType(lhs) || IsFloatingPointType(rhs)) setResultType(MIRType_Double); else return inferFallback(inspector, pc); @@ -1433,7 +1502,7 @@ MBinaryArithInstruction::infer(BaselineInspector *inspector, // Don't specialize values when result isn't double if (lhs == MIRType_Value || rhs == MIRType_Value) { - if (rval != MIRType_Double) { + if (!IsFloatingPointType(rval)) { specialization_ = MIRType_None; return; } @@ -1988,6 +2057,28 @@ MToDouble::foldsTo(bool useValueNumbers) return this; } +MDefinition * +MToFloat32::foldsTo(bool useValueNumbers) +{ + if (input()->type() == MIRType_Float32) + return input(); + + // If x is a Float32, Float32(Double(x)) == x + if (input()->isToDouble() && input()->toToDouble()->input()->type() == MIRType_Float32) + return input()->toToDouble()->input(); + + if (input()->isConstant()) { + const Value &v = input()->toConstant()->value(); + if (v.isNumber()) { + float out = v.toNumber(); + MConstant *c = MConstant::New(DoubleValue(out)); + c->setResultType(MIRType_Float32); + return c; + } + } + return this; +} + MDefinition * MToString::foldsTo(bool useValueNumbers) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 3c3180b5331..f650e65154f 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -453,6 +453,13 @@ class MDefinition : public MNode return !resultTypeSet() || resultTypeSet()->mightBeType(ValueTypeFromMIRType(type)); } + // Float32 specialization operations (see big comment in IonAnalysis before the Float32 + // specialization algorithm). + virtual bool isFloat32Commutative() const { return false; } + virtual bool canProduceFloat32() const { return false; } + virtual bool canConsumeFloat32() const { return false; } + virtual void trySpecializeFloat32() {} + // Returns the beginning of this definition's use chain. MUseIterator usesBegin() const { return uses_.begin(); @@ -948,8 +955,21 @@ class MConstant : public MNullaryInstruction return AliasSet::None(); } + bool updateForReplacement(MDefinition *def) { + MConstant *c = def->toConstant(); + // During constant folding, we don't want to replace a float32 + // value by a double value. + if (type() == MIRType_Float32) + return c->type() == MIRType_Float32; + if (type() == MIRType_Double) + return c->type() != MIRType_Float32; + return true; + } + void computeRange(); bool truncate(); + + bool canProduceFloat32() const; }; class MParameter : public MNullaryInstruction @@ -1723,6 +1743,10 @@ class MCall return getOperand(NumNonArgumentOperands + index); } + void replaceArg(uint32_t index, MDefinition *def) { + replaceOperand(NumNonArgumentOperands + index, def); + } + void rootTargetScript(JSFunction *target) { targetScript_.setRoot(target->nonLazyScript()); } @@ -2641,6 +2665,65 @@ class MToDouble bool isOperandTruncated(size_t index) const; }; +// Converts a primitive (either typed or untyped) to a float32. If the input is +// not primitive at runtime, a bailout occurs. +class MToFloat32 + : public MUnaryInstruction, + public ToDoublePolicy +{ + public: + // Types of values which can be converted. + enum ConversionKind { + NonStringPrimitives, + NonNullNonStringPrimitives, + NumbersOnly + }; + + protected: + ConversionKind conversion_; + + MToFloat32(MDefinition *def, ConversionKind conversion) + : MUnaryInstruction(def), conversion_(conversion) + { + setResultType(MIRType_Float32); + setMovable(); + } + + public: + INSTRUCTION_HEADER(ToFloat32) + static MToFloat32 *New(MDefinition *def, ConversionKind conversion = NonStringPrimitives) { + return new MToFloat32(def, conversion); + } + static MToFloat32 *NewAsmJS(MDefinition *def) { + return new MToFloat32(def, NonStringPrimitives); + } + + ConversionKind conversion() const { + return conversion_; + } + + TypePolicy *typePolicy() { + return this; + } + + virtual MDefinition *foldsTo(bool useValueNumbers); + bool congruentTo(MDefinition *ins) const { + if (!ins->isToFloat32() || ins->toToFloat32()->conversion() != conversion()) + return false; + return congruentIfOperandsEqual(ins); + } + AliasSet getAliasSet() const { + return AliasSet::None(); + } + + void computeRange(); + bool truncate(); + bool isOperandTruncated(size_t index) const; + + bool canConsumeFloat32() const { return true; } + bool canProduceFloat32() const { return true; } +}; + // Converts a uint32 to a double (coming from asm.js). class MAsmJSUnsignedToDouble : public MUnaryInstruction @@ -3106,6 +3189,8 @@ class MBinaryArithInstruction setResultType(MIRType_Int32); } + virtual void trySpecializeFloat32(); + bool congruentTo(MDefinition *ins) const { return MBinaryInstruction::congruentTo(ins); } @@ -3481,6 +3566,8 @@ class MAdd : public MBinaryArithInstruction return add; } + bool isFloat32Commutative() const { return true; } + double getIdentity() { return 0; } @@ -3517,6 +3604,8 @@ class MSub : public MBinaryArithInstruction return 0; } + bool isFloat32Commutative() const { return true; } + bool fallible(); void computeRange(); bool truncate(); @@ -3589,6 +3678,8 @@ class MMul : public MBinaryArithInstruction return canBeNegativeZero_ || canOverflow(); } + bool isFloat32Commutative() const { return true; } + void computeRange(); bool truncate(); bool isOperandTruncated(size_t index) const; @@ -3657,6 +3748,8 @@ class MDiv : public MBinaryArithInstruction return unsigned_; } + bool isFloat32Commutative() const { return true; } + bool fallible(); bool truncate(); }; @@ -3844,6 +3937,8 @@ class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode bool hasBackedgeType_; bool triedToSpecialize_; bool isIterator_; + bool canProduceFloat32_; + bool canConsumeFloat32_; #if DEBUG bool specialized_; @@ -3854,7 +3949,9 @@ class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode : slot_(slot), hasBackedgeType_(false), triedToSpecialize_(false), - isIterator_(false) + isIterator_(false), + canProduceFloat32_(false), + canConsumeFloat32_(false) #if DEBUG , specialized_(false) , capacity_(0) @@ -3952,6 +4049,22 @@ class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode } return first; } + + bool canProduceFloat32() const { + return canProduceFloat32_; + } + + void setCanProduceFloat32(bool can) { + canProduceFloat32_ = can; + } + + bool canConsumeFloat32() const { + return canConsumeFloat32_; + } + + void setCanConsumeFloat32(bool can) { + canConsumeFloat32_ = can; + } }; // The goal of a Beta node is to split a def at a conditionally taken @@ -4956,7 +5069,7 @@ class MStoreElementCommon class MStoreElement : public MAryInstruction<3>, public MStoreElementCommon, - public SingleObjectPolicy + public MixPolicy > { bool needsHoleCheck_; @@ -5006,7 +5119,7 @@ class MStoreElement class MStoreElementHole : public MAryInstruction<4>, public MStoreElementCommon, - public SingleObjectPolicy + public MixPolicy > { MStoreElementHole(MDefinition *object, MDefinition *elements, MDefinition *index, MDefinition *value) { @@ -5206,6 +5319,8 @@ class MLoadTypedArrayElement } void computeRange(); + + bool canProduceFloat32() const { return arrayType_ == ScalarTypeRepresentation::TYPE_FLOAT32; } }; // Load a value from a typed array. Out-of-bounds accesses are handled using @@ -5254,6 +5369,7 @@ class MLoadTypedArrayElementHole AliasSet getAliasSet() const { return AliasSet::Load(AliasSet::TypedArrayElement); } + bool canProduceFloat32() const { return arrayType_ == ScalarTypeRepresentation::TYPE_FLOAT32; } }; // Load a value fallibly or infallibly from a statically known typed array. @@ -5265,7 +5381,9 @@ class MLoadTypedArrayElementStatic : MUnaryInstruction(ptr), typedArray_(typedArray), fallible_(true) { int type = typedArray_->type(); - if (type == ScalarTypeRepresentation::TYPE_FLOAT32 || type == ScalarTypeRepresentation::TYPE_FLOAT64) + if (type == ScalarTypeRepresentation::TYPE_FLOAT32) + setResultType(MIRType_Float32); + else if (type == ScalarTypeRepresentation::TYPE_FLOAT64) setResultType(MIRType_Double); else setResultType(MIRType_Int32); @@ -5304,6 +5422,7 @@ class MLoadTypedArrayElementStatic void computeRange(); bool truncate(); + bool canProduceFloat32() const { return typedArray_->type() == ScalarTypeRepresentation::TYPE_FLOAT32; } }; class MStoreTypedArrayElement @@ -5367,6 +5486,8 @@ class MStoreTypedArrayElement racy_ = true; } bool isOperandTruncated(size_t index) const; + + bool canConsumeFloat32() const { return arrayType_ == ScalarTypeRepresentation::TYPE_FLOAT32; } }; class MStoreTypedArrayElementHole @@ -5430,6 +5551,8 @@ class MStoreTypedArrayElementHole return AliasSet::Store(AliasSet::TypedArrayElement); } bool isOperandTruncated(size_t index) const; + + bool canConsumeFloat32() const { return arrayType_ == ScalarTypeRepresentation::TYPE_FLOAT32; } }; // Store a value infallibly to a statically known typed array. @@ -5471,6 +5594,8 @@ class MStoreTypedArrayElementStatic : return AliasSet::Store(AliasSet::TypedArrayElement); } bool isOperandTruncated(size_t index) const; + + bool canConsumeFloat32() const { return typedArray_->type() == ScalarTypeRepresentation::TYPE_FLOAT32; } }; // Compute an "effective address", i.e., a compound computation of the form: @@ -5590,7 +5715,7 @@ class MLoadFixedSlot class MStoreFixedSlot : public MBinaryInstruction, - public SingleObjectPolicy + public MixPolicy > { bool needsBarrier_; size_t slot_; @@ -6411,7 +6536,7 @@ class MForkJoinSlice // Store to vp[slot] (slots that are not inline in an object). class MStoreSlot : public MBinaryInstruction, - public SingleObjectPolicy + public MixPolicy, NoFloatPolicy<1> > { uint32_t slot_; MIRType slotType_; @@ -6711,7 +6836,7 @@ class MCallSetProperty class MSetPropertyCache : public MSetPropertyInstruction, - public SingleObjectPolicy + public MixPolicy > { bool needsTypeBarrier_; diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index f9c84f2010c..0cd720f25b1 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -76,6 +76,7 @@ namespace jit { _(GuardString) \ _(AssertRange) \ _(ToDouble) \ + _(ToFloat32) \ _(ToInt32) \ _(TruncateToInt32) \ _(ToString) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index 434cda3f8f4..5680cc1bd68 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -163,6 +163,7 @@ class ParallelSafetyVisitor : public MInstructionVisitor SAFE_OP(Unbox) SAFE_OP(GuardObject) SAFE_OP(ToDouble) + SAFE_OP(ToFloat32) SAFE_OP(ToInt32) SAFE_OP(TruncateToInt32) SAFE_OP(MaybeToDoubleElement) diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 08e2fc41d83..94e440b9f1a 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1105,6 +1105,12 @@ MToDouble::computeRange() setRange(new Range(getOperand(0))); } +void +MToFloat32::computeRange() +{ + setRange(new Range(getOperand(0))); +} + void MTruncateToInt32::computeRange() { @@ -1829,6 +1835,20 @@ MToDouble::truncate() return true; } +bool +MToFloat32::truncate() +{ + JS_ASSERT(type() == MIRType_Float32); + + // We use the return type to flag that this MToFloat32 sould be replaced by a + // MTruncateToInt32 when modifying the graph. + setResultType(MIRType_Int32); + if (range()) + range()->wrapAroundToInt32(); + + return true; +} + bool MLoadTypedArrayElementStatic::truncate() { @@ -1880,6 +1900,14 @@ MToDouble::isOperandTruncated(size_t index) const return type() == MIRType_Int32; } +bool +MToFloat32::isOperandTruncated(size_t index) const +{ + // The return type is used to flag that we are replacing this Float32 by a + // Truncate of its operand if needed. + return type() == MIRType_Int32; +} + bool MStoreTypedArrayElement::isOperandTruncated(size_t index) const { diff --git a/js/src/jit/SnapshotReader.h b/js/src/jit/SnapshotReader.h index 87f744e081e..9f533e73546 100644 --- a/js/src/jit/SnapshotReader.h +++ b/js/src/jit/SnapshotReader.h @@ -57,6 +57,8 @@ class SnapshotReader { CONSTANT, // An index into the constant pool. DOUBLE_REG, // Type is double, payload is in a register. + FLOAT32_REG, // Type is float32, payload is in a register. + FLOAT32_STACK, // Type is float32, payload is on the stack. TYPED_REG, // Type is constant, payload is in a register. TYPED_STACK, // Type is constant, payload is on the stack. UNTYPED, // Type is not known. @@ -135,6 +137,18 @@ class SnapshotReader { fpu_ = reg.code(); } + Slot(SlotMode mode, const FloatRegister ®) + : mode_(mode) + { + JS_ASSERT(mode == FLOAT32_REG); + fpu_ = reg.code(); + } + Slot(SlotMode mode, const Location &loc) + : mode_(mode) + { + JS_ASSERT(mode == FLOAT32_STACK); + known_type_.payload = loc; + } Slot(SlotMode mode) : mode_(mode) { } @@ -166,11 +180,11 @@ class SnapshotReader return known_type_.payload.reg(); } FloatRegister floatReg() const { - JS_ASSERT(mode() == DOUBLE_REG); + JS_ASSERT(mode() == DOUBLE_REG || mode() == FLOAT32_REG); return FloatRegister::FromCode(fpu_); } int32_t stackSlot() const { - JS_ASSERT(mode() == TYPED_STACK); + JS_ASSERT(mode() == TYPED_STACK || mode() == FLOAT32_STACK); return known_type_.payload.stackSlot(); } #if defined(JS_NUNBOX32) diff --git a/js/src/jit/SnapshotWriter.h b/js/src/jit/SnapshotWriter.h index af6c039db44..4b174d24a1a 100644 --- a/js/src/jit/SnapshotWriter.h +++ b/js/src/jit/SnapshotWriter.h @@ -46,6 +46,8 @@ class SnapshotWriter void addUndefinedSlot(); void addNullSlot(); void addInt32Slot(int32_t value); + void addFloat32Slot(const FloatRegister ®); + void addFloat32Slot(int32_t stackIndex); void addConstantPoolSlot(uint32_t index); #if defined(JS_NUNBOX32) void addSlot(const Register &type, const Register &payload); diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp index 03cc029398c..42e052db3ef 100644 --- a/js/src/jit/Snapshots.cpp +++ b/js/src/jit/Snapshots.cpp @@ -56,13 +56,16 @@ using namespace js::jit; // // JSVAL_TYPE_NULL: // Reg value: -// 0-29: Constant integer; Int32Value(n) +// 0-27: Constant integer; Int32Value(n) +// 28: Variable float32; Float register code +// 29: Variable float32; Stack index // 30: NullValue() // 31: Constant integer; Int32Value([vws]) // // JSVAL_TYPE_UNDEFINED: // Reg value: -// 0-29: Constant value, index n into ionScript->constants() +// 0-27: Constant value, index n into ionScript->constants() +// 28-29: unused // 30: UndefinedValue() // 31: Constant value, index [vwu] into // ionScript->constants() @@ -172,10 +175,12 @@ static const uint32_t NUNBOX32_REG_REG = 3; static const uint32_t MAX_TYPE_FIELD_VALUE = 7; -static const uint32_t MAX_REG_FIELD_VALUE = 31; -static const uint32_t ESC_REG_FIELD_INDEX = 31; -static const uint32_t ESC_REG_FIELD_CONST = 30; -static const uint32_t MIN_REG_FIELD_ESC = 30; +static const uint32_t MAX_REG_FIELD_VALUE = 31; +static const uint32_t ESC_REG_FIELD_INDEX = 31; +static const uint32_t ESC_REG_FIELD_CONST = 30; +static const uint32_t ESC_REG_FIELD_FLOAT32_STACK = 29; +static const uint32_t ESC_REG_FIELD_FLOAT32_REG = 28; +static const uint32_t MIN_REG_FIELD_ESC = 28; SnapshotReader::Slot SnapshotReader::readSlot() @@ -210,6 +215,10 @@ SnapshotReader::readSlot() return Slot(JS_NULL); if (code == ESC_REG_FIELD_INDEX) return Slot(JS_INT32, reader_.readSigned()); + if (code == ESC_REG_FIELD_FLOAT32_REG) + return Slot(FLOAT32_REG, FloatRegister::FromCode(reader_.readUnsigned())); + if (code == ESC_REG_FIELD_FLOAT32_STACK) + return Slot(FLOAT32_STACK, Location::From(reader_.readSigned())); return Slot(JS_INT32, code); case JSVAL_TYPE_UNDEFINED: @@ -353,6 +362,7 @@ SnapshotWriter::writeSlotHeader(JSValueType type, uint32_t regCode) void SnapshotWriter::addSlot(const FloatRegister ®) { + JS_ASSERT(reg.code() < MIN_REG_FIELD_ESC); IonSpew(IonSpew_Snapshots, " slot %u: double (reg %s)", slotsWritten_, reg.name()); writeSlotHeader(JSVAL_TYPE_DOUBLE, reg.code()); @@ -506,6 +516,23 @@ SnapshotWriter::addInt32Slot(int32_t value) } } +void +SnapshotWriter::addFloat32Slot(const FloatRegister ®) +{ + JS_ASSERT(reg.code() < MIN_REG_FIELD_ESC); + IonSpew(IonSpew_Snapshots, " slot %u: float32 (reg %s)", slotsWritten_, reg.name()); + writeSlotHeader(JSVAL_TYPE_NULL, ESC_REG_FIELD_FLOAT32_REG); + writer_.writeUnsigned(reg.code()); +} + +void +SnapshotWriter::addFloat32Slot(int32_t stackIndex) +{ + IonSpew(IonSpew_Snapshots, " slot %u: float32 (stack %d)", slotsWritten_, stackIndex); + writeSlotHeader(JSVAL_TYPE_NULL, ESC_REG_FIELD_FLOAT32_STACK); + writer_.writeSigned(stackIndex); +} + void SnapshotWriter::addConstantPoolSlot(uint32_t index) { diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 227ad688166..11a5c773f8a 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -6,6 +6,7 @@ #include "jit/TypePolicy.h" +#include "jit/Lowering.h" #include "jit/MIR.h" #include "jit/MIRGraph.h" @@ -17,7 +18,20 @@ BoxInputsPolicy::boxAt(MInstruction *at, MDefinition *operand) { if (operand->isUnbox()) return operand->toUnbox()->input(); - MBox *box = MBox::New(operand); + return alwaysBoxAt(at, operand); +} + +MDefinition * +BoxInputsPolicy::alwaysBoxAt(MInstruction *at, MDefinition *operand) +{ + MDefinition *boxedOperand = operand; + // Replace Float32 by double + if (operand->type() == MIRType_Float32) { + MInstruction *replace = MToDouble::New(operand); + operand->block()->insertBefore(at, replace); + boxedOperand = replace; + } + MBox *box = MBox::New(boxedOperand); at->block()->insertBefore(at, box); return box; } @@ -40,7 +54,7 @@ ArithPolicy::adjustInputs(MInstruction *ins) if (specialization_ == MIRType_None) return BoxInputsPolicy::adjustInputs(ins); - JS_ASSERT(ins->type() == MIRType_Double || ins->type() == MIRType_Int32); + JS_ASSERT(ins->type() == MIRType_Double || ins->type() == MIRType_Int32 || ins->type() == MIRType_Float32); for (size_t i = 0, e = ins->numOperands(); i < e; i++) { MDefinition *in = ins->getOperand(i); @@ -59,6 +73,8 @@ ArithPolicy::adjustInputs(MInstruction *ins) if (ins->type() == MIRType_Double) replace = MToDouble::New(in); + else if (ins->type() == MIRType_Float32) + replace = MToFloat32::New(in); else replace = MToInt32::New(in); @@ -99,6 +115,16 @@ ComparePolicy::adjustInputs(MInstruction *def) JS_ASSERT(def->isCompare()); MCompare *compare = def->toCompare(); + // Convert Float32 operands to doubles + for (size_t i = 0; i < 2; i++) { + MDefinition *in = def->getOperand(i); + if (in->type() == MIRType_Float32) { + MInstruction *replace = MToDouble::New(in); + def->block()->insertBefore(def, replace); + def->replaceOperand(i, replace); + } + } + // Box inputs to get value if (compare->compareType() == MCompare::Compare_Unknown || compare->compareType() == MCompare::Compare_Value) @@ -313,6 +339,13 @@ BitwisePolicy::adjustInputs(MInstruction *ins) if (in->type() == MIRType_Object || in->type() == MIRType_String) in = boxAt(ins, in); + if (in->type() == MIRType_Float32) { + MToDouble *replace = MToDouble::New(in); + ins->block()->insertBefore(ins, replace); + ins->replaceOperand(i, replace); + in = replace; + } + MInstruction *replace = MTruncateToInt32::New(in); ins->block()->insertBefore(ins, replace); ins->replaceOperand(i, replace); @@ -410,6 +443,61 @@ DoublePolicy::staticAdjustInputs(MInstruction *def) template bool DoublePolicy<0>::staticAdjustInputs(MInstruction *def); template bool DoublePolicy<1>::staticAdjustInputs(MInstruction *def); +template +bool +Float32Policy::staticAdjustInputs(MInstruction *def) +{ + MDefinition *in = def->getOperand(Op); + if (in->type() == MIRType_Float32) + return true; + + // Force a bailout. Objects may be effectful; strings are currently unhandled. + if (in->type() == MIRType_Object || in->type() == MIRType_String) { + MToDouble *toDouble = MToDouble::New(in); + def->block()->insertBefore(def, toDouble); + + MBox *box = MBox::New(toDouble); + def->block()->insertBefore(def, box); + + MUnbox *unbox = MUnbox::New(box, MIRType_Double, MUnbox::Fallible); + def->block()->insertBefore(def, unbox); + + MToFloat32 *toFloat32 = MToFloat32::New(unbox); + def->block()->insertBefore(def, toFloat32); + + def->replaceOperand(Op, unbox); + + return true; + } + + MToFloat32 *replace = MToFloat32::New(in); + def->block()->insertBefore(def, replace); + def->replaceOperand(Op, replace); + return true; +} + +template bool Float32Policy<0>::staticAdjustInputs(MInstruction *def); +template bool Float32Policy<1>::staticAdjustInputs(MInstruction *def); +template bool Float32Policy<2>::staticAdjustInputs(MInstruction *def); + +template +bool +NoFloatPolicy::staticAdjustInputs(MInstruction *def) +{ + MDefinition *in = def->getOperand(Op); + if (in->type() == MIRType_Float32) { + MToDouble *replace = MToDouble::New(in); + def->block()->insertBefore(def, replace); + def->replaceOperand(Op, replace); + } + return true; +} + +template bool NoFloatPolicy<0>::staticAdjustInputs(MInstruction *def); +template bool NoFloatPolicy<1>::staticAdjustInputs(MInstruction *def); +template bool NoFloatPolicy<2>::staticAdjustInputs(MInstruction *def); +template bool NoFloatPolicy<3>::staticAdjustInputs(MInstruction *def); + template bool BoxPolicy::staticAdjustInputs(MInstruction *ins) @@ -468,6 +556,18 @@ CallPolicy::adjustInputs(MInstruction *ins) { MCall *call = ins->toCall(); + // External calls shouldn't have Float32 parameters + for (uint32_t i = 0, numArgs = call->numActualArgs(); i < numArgs; i++) { + MDefinition *arg = call->getArg(i+1); // arg 0 is |this| + if (arg->type() == MIRType_Float32) { + JS_ASSERT(arg->isPassArg()); // can't be a type barrier, as Float32 doesn't rely on type inference + MPassArg *passArg = arg->toPassArg(); + MInstruction *replace = MToDouble::New(passArg->getArgument()); + passArg->replaceOperand(0, replace); + call->block()->insertBefore(passArg, replace); + } + } + MDefinition *func = call->getFunction(); if (func->type() == MIRType_Object) return true; @@ -521,6 +621,7 @@ StoreTypedArrayPolicy::adjustValueInput(MInstruction *ins, int arrayType, switch (value->type()) { case MIRType_Int32: case MIRType_Double: + case MIRType_Float32: case MIRType_Boolean: case MIRType_Value: break; @@ -550,6 +651,7 @@ StoreTypedArrayPolicy::adjustValueInput(MInstruction *ins, int arrayType, JS_ASSERT(value->type() == MIRType_Int32 || value->type() == MIRType_Boolean || value->type() == MIRType_Double || + value->type() == MIRType_Float32 || value->type() == MIRType_Value); switch (arrayType) { @@ -569,6 +671,15 @@ StoreTypedArrayPolicy::adjustValueInput(MInstruction *ins, int arrayType, JS_ASSERT(value->type() == MIRType_Int32); break; case ScalarTypeRepresentation::TYPE_FLOAT32: + if (LIRGenerator::allowFloat32Optimizations()) { + if (value->type() != MIRType_Float32) { + value = MToFloat32::New(value); + ins->block()->insertBefore(ins, value->toInstruction()); + } + break; + } + // Fallthrough: if the LIRGenerator cannot directly store Float32, it will expect the + // stored value to be a double. case ScalarTypeRepresentation::TYPE_FLOAT64: if (value->type() != MIRType_Double) { value = MToDouble::New(value); diff --git a/js/src/jit/TypePolicy.h b/js/src/jit/TypePolicy.h index 6313a53d680..f73623be420 100644 --- a/js/src/jit/TypePolicy.h +++ b/js/src/jit/TypePolicy.h @@ -35,6 +35,7 @@ class BoxInputsPolicy : public TypePolicy static MDefinition *boxAt(MInstruction *at, MDefinition *operand); public: + static MDefinition *alwaysBoxAt(MInstruction *at, MDefinition *operand); virtual bool adjustInputs(MInstruction *def); }; @@ -144,6 +145,45 @@ class DoublePolicy : public BoxInputsPolicy } }; +// Expect a float32 for operand Op. If the input is a Value, it is unboxed. +template +class Float32Policy : public BoxInputsPolicy +{ + public: + static bool staticAdjustInputs(MInstruction *def); + bool adjustInputs(MInstruction *def) { + return staticAdjustInputs(def); + } +}; + +// Expect a float32 OR a double for operand Op, but will prioritize Float32 +// if the result type is set as such. If the input is a Value, it is unboxed. +template +class RuntimePolicy : public TypePolicy +{ + MIRType policyType_; + + public: + bool adjustInputs(MInstruction *def) { + if (policyType_ == MIRType_Double) + return DoublePolicy::staticAdjustInputs(def); + return Float32Policy::staticAdjustInputs(def); + } + void setPolicyType(MIRType type) { + policyType_ = type; + } +}; + +template +class NoFloatPolicy +{ + public: + static bool staticAdjustInputs(MInstruction *def); + bool adjustInputs(MInstruction *def) { + return staticAdjustInputs(def); + } +}; + // Box objects or strings as an input to a ToDouble instruction. class ToDoublePolicy : public BoxInputsPolicy { @@ -251,7 +291,7 @@ class ClampPolicy : public BoxInputsPolicy static inline bool CoercesToDouble(MIRType type) { - if (type == MIRType_Undefined || type == MIRType_Double) + if (type == MIRType_Undefined || IsFloatingPointType(type)) return true; return false; } diff --git a/js/src/jit/arm/Lowering-arm.h b/js/src/jit/arm/Lowering-arm.h index 882ecb86b1b..4dbfcf0e311 100644 --- a/js/src/jit/arm/Lowering-arm.h +++ b/js/src/jit/arm/Lowering-arm.h @@ -67,6 +67,13 @@ class LIRGeneratorARM : public LIRGeneratorShared LGetPropertyCacheT *newLGetPropertyCacheT(MGetPropertyCache *ins); LGetElementCacheT *newLGetElementCacheT(MGetElementCache *ins); + bool lowerConstantFloat32(float d, MInstruction *ins) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + bool lowerTruncateFToInt32(MTruncateToInt32 *ins) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + public: bool visitConstant(MConstant *ins); bool visitBox(MBox *box); diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 6c998d9fd19..9541ff7a2b1 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -54,6 +54,19 @@ class MacroAssemblerARM : public Assembler void convertDoubleToInt32(const FloatRegister &src, const Register &dest, Label *fail, bool negativeZeroCheck = true); + void convertFloatToDouble(const FloatRegister &src, const FloatRegister &dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void branchTruncateFloat32(const FloatRegister &src, const Register &dest, Label *fail) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void convertInt32ToFloat32(const Register &src, const FloatRegister &dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void convertInt32ToFloat32(const Address &src, FloatRegister dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void addDouble(FloatRegister src, FloatRegister dest); void subDouble(FloatRegister src, FloatRegister dest); void mulDouble(FloatRegister src, FloatRegister dest); @@ -748,6 +761,16 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testDoubleTruthy(bool truthy, const FloatRegister ®); Condition testStringTruthy(bool truthy, const ValueOperand &value); + void boolValueToFloat32(const ValueOperand &operand, const FloatRegister &dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void int32ValueToFloat32(const ValueOperand &operand, const FloatRegister &dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void loadStaticFloat32(const float *dp, const FloatRegister &dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + template void branchTestInt32(Condition cond, const T & t, Label *label) { Condition c = testInt32(cond, t); @@ -1187,6 +1210,13 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void loadFloatAsDouble(const Address &addr, const FloatRegister &dest); void loadFloatAsDouble(const BaseIndex &src, const FloatRegister &dest); + void loadFloat(const Address &addr, const FloatRegister &dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void loadFloat(const BaseIndex &src, const FloatRegister &dest) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void store8(const Register &src, const Address &address); void store8(const Imm32 &imm, const Address &address); void store8(const Register &src, const BaseIndex &address); @@ -1269,6 +1299,11 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void branchDouble(DoubleCondition cond, const FloatRegister &lhs, const FloatRegister &rhs, Label *label); + void branchFloat(DoubleCondition cond, const FloatRegister &lhs, const FloatRegister &rhs, + Label *label) { + MOZ_ASSUME_UNREACHABLE("NYI"); + } + void checkStackAlignment(); void rshiftPtr(Imm32 imm, Register dest) { diff --git a/js/src/jit/shared/Assembler-x86-shared.h b/js/src/jit/shared/Assembler-x86-shared.h index 7603fac6778..104e5c55a73 100644 --- a/js/src/jit/shared/Assembler-x86-shared.h +++ b/js/src/jit/shared/Assembler-x86-shared.h @@ -227,6 +227,10 @@ class AssemblerX86Shared label->bind(masm.size()); masm.doubleConstant(d); } + void writeFloatConstant(float f, Label *label) { + label->bind(masm.size()); + masm.floatConstant(f); + } void movl(const Imm32 &imm32, const Register &dest) { masm.movl_i32r(imm32.value, dest.code()); } @@ -332,6 +336,9 @@ class AssemblerX86Shared void movss(const Operand &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); switch (src.kind()) { + case Operand::FPREG: + masm.movss_rr(src.fpu(), dest.code()); + break; case Operand::REG_DISP: masm.movss_mr(src.disp(), src.base(), dest.code()); break; @@ -345,6 +352,9 @@ class AssemblerX86Shared void movss(const FloatRegister &src, const Operand &dest) { JS_ASSERT(HasSSE2()); switch (dest.kind()) { + case Operand::FPREG: + masm.movss_rr(src.code(), dest.fpu()); + break; case Operand::REG_DISP: masm.movss_rm(src.code(), dest.disp(), dest.base()); break; @@ -1141,6 +1151,30 @@ class AssemblerX86Shared JS_ASSERT(HasSSE2()); masm.cvttsd2si_rr(src.code(), dest.code()); } + void cvttss2si(const FloatRegister &src, const Register &dest) { + JS_ASSERT(HasSSE2()); + masm.cvttss2si_rr(src.code(), dest.code()); + } + void cvtsi2ss(const Operand &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + switch (src.kind()) { + case Operand::REG: + masm.cvtsi2ss_rr(src.reg(), dest.code()); + break; + case Operand::REG_DISP: + masm.cvtsi2ss_mr(src.disp(), src.base(), dest.code()); + break; + case Operand::SCALE: + masm.cvtsi2ss_mr(src.disp(), src.base(), src.index(), src.scale(), dest.code()); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); + } + } + void cvtsi2ss(const Register &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + masm.cvtsi2ss_rr(src.code(), dest.code()); + } void cvtsi2sd(const Register &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); masm.cvtsi2sd_rr(src.code(), dest.code()); @@ -1157,6 +1191,10 @@ class AssemblerX86Shared JS_ASSERT(HasSSE2()); masm.ucomisd_rr(rhs.code(), lhs.code()); } + void ucomiss(const FloatRegister &lhs, const FloatRegister &rhs) { + JS_ASSERT(HasSSE2()); + masm.ucomiss_rr(rhs.code(), lhs.code()); + } void pcmpeqw(const FloatRegister &lhs, const FloatRegister &rhs) { JS_ASSERT(HasSSE2()); masm.pcmpeqw_rr(rhs.code(), lhs.code()); @@ -1173,6 +1211,10 @@ class AssemblerX86Shared JS_ASSERT(HasSSE2()); masm.addsd_rr(src.code(), dest.code()); } + void addss(const FloatRegister &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + masm.addss_rr(src.code(), dest.code()); + } void addsd(const Operand &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); switch (src.kind()) { @@ -1186,6 +1228,24 @@ class AssemblerX86Shared case Operand::ADDRESS: masm.addsd_mr(src.address(), dest.code()); break; +#endif + default: + MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); + } + } + void addss(const Operand &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + switch (src.kind()) { + case Operand::FPREG: + masm.addss_rr(src.fpu(), dest.code()); + break; + case Operand::REG_DISP: + masm.addss_mr(src.disp(), src.base(), dest.code()); + break; +#ifdef JS_CPU_X86 + case Operand::ADDRESS: + masm.addss_mr(src.address(), dest.code()); + break; #endif default: MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); @@ -1195,6 +1255,10 @@ class AssemblerX86Shared JS_ASSERT(HasSSE2()); masm.subsd_rr(src.code(), dest.code()); } + void subss(const FloatRegister &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + masm.subss_rr(src.code(), dest.code()); + } void subsd(const Operand &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); switch (src.kind()) { @@ -1208,6 +1272,19 @@ class AssemblerX86Shared MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); } } + void subss(const Operand &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + switch (src.kind()) { + case Operand::FPREG: + masm.subss_rr(src.fpu(), dest.code()); + break; + case Operand::REG_DISP: + masm.subss_mr(src.disp(), src.base(), dest.code()); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); + } + } void mulsd(const FloatRegister &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); masm.mulsd_rr(src.code(), dest.code()); @@ -1225,10 +1302,31 @@ class AssemblerX86Shared MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); } } + void mulss(const Operand &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + switch (src.kind()) { + case Operand::FPREG: + masm.mulss_rr(src.fpu(), dest.code()); + break; + case Operand::REG_DISP: + masm.mulss_mr(src.disp(), src.base(), dest.code()); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); + } + } + void mulss(const FloatRegister &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + masm.mulss_rr(src.code(), dest.code()); + } void divsd(const FloatRegister &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); masm.divsd_rr(src.code(), dest.code()); } + void divss(const FloatRegister &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + masm.divss_rr(src.code(), dest.code()); + } void divsd(const Operand &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); switch (src.kind()) { @@ -1242,10 +1340,27 @@ class AssemblerX86Shared MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); } } + void divss(const Operand &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + switch (src.kind()) { + case Operand::FPREG: + masm.divss_rr(src.fpu(), dest.code()); + break; + case Operand::REG_DISP: + masm.divss_mr(src.disp(), src.base(), dest.code()); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected operand kind"); + } + } void xorpd(const FloatRegister &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); masm.xorpd_rr(src.code(), dest.code()); } + void xorps(const FloatRegister &src, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + masm.xorps_rr(src.code(), dest.code()); + } void orpd(const FloatRegister &src, const FloatRegister &dest) { JS_ASSERT(HasSSE2()); masm.orpd_rr(src.code(), dest.code()); diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index 51448699ce6..975fb0d2ee8 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -162,15 +162,23 @@ CodeGeneratorShared::encodeSlots(LSnapshot *snapshot, MResumePoint *resumePoint, case MIRType_Object: case MIRType_Boolean: case MIRType_Double: + case MIRType_Float32: { LAllocation *payload = snapshot->payloadOfSlot(i); - JSValueType type = ValueTypeFromMIRType(mir->type()); + JSValueType valueType = ValueTypeFromMIRType(type); if (payload->isMemory()) { - snapshots_.addSlot(type, ToStackIndex(payload)); + if (type == MIRType_Float32) + snapshots_.addFloat32Slot(ToStackIndex(payload)); + else + snapshots_.addSlot(valueType, ToStackIndex(payload)); } else if (payload->isGeneralReg()) { - snapshots_.addSlot(type, ToRegister(payload)); + snapshots_.addSlot(valueType, ToRegister(payload)); } else if (payload->isFloatReg()) { - snapshots_.addSlot(ToFloatRegister(payload)); + FloatRegister reg = ToFloatRegister(payload); + if (type == MIRType_Float32) + snapshots_.addFloat32Slot(reg); + else + snapshots_.addSlot(reg); } else { MConstant *constant = mir->toConstant(); const Value &v = constant->value(); diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.cpp b/js/src/jit/shared/CodeGenerator-x86-shared.cpp index 629ecc1290d..fabbb663ca0 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -89,6 +89,14 @@ CodeGeneratorX86Shared::visitDouble(LDouble *ins) return true; } +bool +CodeGeneratorX86Shared::visitFloat32(LFloat32 *ins) +{ + const LDefinition *out = ins->getDef(0); + masm.loadConstantFloat32(ins->getFloat(), ToFloatRegister(out)); + return true; +} + bool CodeGeneratorX86Shared::visitTestIAndBranch(LTestIAndBranch *test) { @@ -1276,6 +1284,34 @@ CodeGeneratorX86Shared::visitMathD(LMathD *math) return true; } +bool +CodeGeneratorX86Shared::visitMathF(LMathF *math) +{ + FloatRegister lhs = ToFloatRegister(math->lhs()); + Operand rhs = ToOperand(math->rhs()); + + JS_ASSERT(ToFloatRegister(math->output()) == lhs); + + switch (math->jsop()) { + case JSOP_ADD: + masm.addss(rhs, lhs); + break; + case JSOP_SUB: + masm.subss(rhs, lhs); + break; + case JSOP_MUL: + masm.mulss(rhs, lhs); + break; + case JSOP_DIV: + masm.divss(rhs, lhs); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected opcode"); + return false; + } + return true; +} + bool CodeGeneratorX86Shared::visitFloor(LFloor *lir) { @@ -1527,6 +1563,16 @@ CodeGeneratorX86Shared::visitNegD(LNegD *ins) return true; } +bool +CodeGeneratorX86Shared::visitNegF(LNegF *ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + JS_ASSERT(input == ToFloatRegister(ins->output())); + + masm.negateFloat(input); + return true; +} + } // namespace jit } // namespace js diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.h b/js/src/jit/shared/CodeGenerator-x86-shared.h index 21f569d2269..a6686fb3954 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.h +++ b/js/src/jit/shared/CodeGenerator-x86-shared.h @@ -79,6 +79,7 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared public: // Instruction visitors. virtual bool visitDouble(LDouble *ins); + virtual bool visitFloat32(LFloat32 *ins); virtual bool visitMinMaxD(LMinMaxD *ins); virtual bool visitAbsD(LAbsD *ins); virtual bool visitSqrtD(LSqrtD *ins); @@ -105,6 +106,7 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared virtual bool visitNotI(LNotI *comp); virtual bool visitNotD(LNotD *comp); virtual bool visitMathD(LMathD *math); + virtual bool visitMathF(LMathF *math); virtual bool visitFloor(LFloor *lir); virtual bool visitRound(LRound *lir); virtual bool visitGuardShape(LGuardShape *guard); @@ -116,6 +118,7 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared bool visitNegI(LNegI *lir); bool visitNegD(LNegD *lir); + bool visitNegF(LNegF *lir); // Out of line visitors. bool visitOutOfLineBailout(OutOfLineBailout *ool); diff --git a/js/src/jit/shared/Lowering-shared-inl.h b/js/src/jit/shared/Lowering-shared-inl.h index 463c292b803..1cbf12f5334 100644 --- a/js/src/jit/shared/Lowering-shared-inl.h +++ b/js/src/jit/shared/Lowering-shared-inl.h @@ -140,6 +140,7 @@ LIRGeneratorShared::defineReturn(LInstruction *lir, MDefinition *mir) lir->setDef(0, LDefinition(vreg, LDefinition::BOX, LGeneralReg(JSReturnReg))); #endif break; + case MIRType_Float32: case MIRType_Double: lir->setDef(0, LDefinition(vreg, LDefinition::DOUBLE, LFloatReg(ReturnFloatReg))); break; @@ -274,7 +275,7 @@ LIRGeneratorShared::useRegisterOrNonNegativeConstantAtStart(MDefinition *mir) LAllocation LIRGeneratorShared::useRegisterOrNonDoubleConstant(MDefinition *mir) { - if (mir->isConstant() && mir->type() != MIRType_Double) + if (mir->isConstant() && mir->type() != MIRType_Double && mir->type() != MIRType_Float32) return LAllocation(mir->toConstant()->vp()); return useRegister(mir); } @@ -413,7 +414,7 @@ VirtualRegisterOfPayload(MDefinition *mir) { if (mir->isBox()) { MDefinition *inner = mir->toBox()->getOperand(0); - if (!inner->isConstant() && inner->type() != MIRType_Double) + if (!inner->isConstant() && inner->type() != MIRType_Double && inner->type() != MIRType_Float32) return inner->virtualRegister(); } if (mir->isTypeBarrier()) diff --git a/js/src/jit/shared/Lowering-shared.h b/js/src/jit/shared/Lowering-shared.h index 7f42cc80630..4308de715b9 100644 --- a/js/src/jit/shared/Lowering-shared.h +++ b/js/src/jit/shared/Lowering-shared.h @@ -181,6 +181,11 @@ class LIRGeneratorShared : public MInstructionVisitorWithDefaults static bool allowStaticTypedArrayAccesses() { return false; } + + // Whether we can emit Float32 specific optimizations. + static bool allowFloat32Optimizations() { + return false; + } }; } // namespace jit diff --git a/js/src/jit/shared/Lowering-x86-shared.cpp b/js/src/jit/shared/Lowering-x86-shared.cpp index 31e6e01e0e7..fb364fadd98 100644 --- a/js/src/jit/shared/Lowering-x86-shared.cpp +++ b/js/src/jit/shared/Lowering-x86-shared.cpp @@ -264,12 +264,21 @@ LIRGeneratorX86Shared::lowerConstantDouble(double d, MInstruction *mir) return define(new LDouble(d), mir); } +bool +LIRGeneratorX86Shared::lowerConstantFloat32(float f, MInstruction *mir) +{ + return define(new LFloat32(f), mir); +} + bool LIRGeneratorX86Shared::visitConstant(MConstant *ins) { if (ins->type() == MIRType_Double) return lowerConstantDouble(ins->value().toDouble(), ins); + if (ins->type() == MIRType_Float32) + return lowerConstantFloat32(ins->value().toDouble(), ins); + // Emit non-double constants at their uses. if (ins->canEmitAtUses()) return emitAtUses(ins); diff --git a/js/src/jit/shared/Lowering-x86-shared.h b/js/src/jit/shared/Lowering-x86-shared.h index bda85bb2660..c2bf3cdaf3e 100644 --- a/js/src/jit/shared/Lowering-x86-shared.h +++ b/js/src/jit/shared/Lowering-x86-shared.h @@ -46,6 +46,7 @@ class LIRGeneratorX86Shared : public LIRGeneratorShared bool lowerUMod(MInstruction *mod); bool lowerUrshD(MUrsh *mir); bool lowerConstantDouble(double d, MInstruction *ins); + bool lowerConstantFloat32(float d, MInstruction *ins); bool lowerTruncateDToInt32(MTruncateToInt32 *ins); }; diff --git a/js/src/jit/shared/MacroAssembler-x86-shared.h b/js/src/jit/shared/MacroAssembler-x86-shared.h index 381487d9410..ebaea6b60ec 100644 --- a/js/src/jit/shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/shared/MacroAssembler-x86-shared.h @@ -62,6 +62,34 @@ class MacroAssemblerX86Shared : public Assembler j(ConditionFromDoubleCondition(cond), label); } + void compareFloat(DoubleCondition cond, const FloatRegister &lhs, const FloatRegister &rhs) { + if (cond & DoubleConditionBitInvert) + ucomiss(rhs, lhs); + else + ucomiss(lhs, rhs); + } + void branchFloat(DoubleCondition cond, const FloatRegister &lhs, + const FloatRegister &rhs, Label *label) + { + compareFloat(cond, lhs, rhs); + + if (cond == DoubleEqual) { + Label unordered; + j(Parity, &unordered); + j(Equal, label); + bind(&unordered); + return; + } + if (cond == DoubleNotEqualOrUnordered) { + j(NotEqual, label); + j(Parity, label); + return; + } + + JS_ASSERT(!(cond & DoubleConditionBitSpecial)); + j(ConditionFromDoubleCondition(cond), label); + } + void move32(const Imm32 &imm, const Register &dest) { if (imm.value == 0) xorl(dest, dest); @@ -211,6 +239,12 @@ class MacroAssemblerX86Shared : public Assembler void convertInt32ToDouble(const Address &src, FloatRegister dest) { cvtsi2sd(Operand(src), dest); } + void convertInt32ToFloat32(const Register &src, const FloatRegister &dest) { + cvtsi2ss(src, dest); + } + void convertInt32ToFloat32(const Address &src, FloatRegister dest) { + cvtsi2ss(Operand(src), dest); + } Condition testDoubleTruthy(bool truthy, const FloatRegister ®) { xorpd(ScratchFloatReg, ScratchFloatReg); ucomisd(ScratchFloatReg, reg); @@ -293,6 +327,13 @@ class MacroAssemblerX86Shared : public Assembler // XOR the float in a float register with -0.0. xorpd(ScratchFloatReg, reg); // s ^ 0x80000000000000 } + void negateFloat(FloatRegister reg) { + pcmpeqw(ScratchFloatReg, ScratchFloatReg); + psllq(Imm32(31), ScratchFloatReg); + + // XOR the float in a float register with -0.0. + xorps(ScratchFloatReg, reg); // s ^ 0x80000000 + } void addDouble(FloatRegister src, FloatRegister dest) { addsd(src, dest); } @@ -305,6 +346,9 @@ class MacroAssemblerX86Shared : public Assembler void divDouble(FloatRegister src, FloatRegister dest) { divsd(src, dest); } + void convertFloatToDouble(const FloatRegister &src, const FloatRegister &dest) { + cvtss2sd(src, dest); + } void convertDoubleToFloat(const FloatRegister &src, const FloatRegister &dest) { cvtsd2ss(src, dest); } @@ -324,6 +368,18 @@ class MacroAssemblerX86Shared : public Assembler movss(src, dest); cvtss2sd(dest, dest); } + void loadFloat(const Register &src, FloatRegister dest) { + movss(Operand(src), dest); + } + void loadFloat(const Address &src, FloatRegister dest) { + movss(Operand(src), dest); + } + void loadFloat(const BaseIndex &src, FloatRegister dest) { + movss(Operand(src), dest); + } + void loadFloat(const Operand &src, FloatRegister dest) { + movss(src, dest); + } void storeFloat(FloatRegister src, const Address &dest) { movss(src, Operand(dest)); } diff --git a/js/src/jit/x64/Lowering-x64.cpp b/js/src/jit/x64/Lowering-x64.cpp index cbc6aa7accb..c6ca60c4fd3 100644 --- a/js/src/jit/x64/Lowering-x64.cpp +++ b/js/src/jit/x64/Lowering-x64.cpp @@ -104,10 +104,12 @@ LIRGeneratorX64::visitStoreTypedArrayElement(MStoreTypedArrayElement *ins) JS_ASSERT(ins->elements()->type() == MIRType_Elements); JS_ASSERT(ins->index()->type() == MIRType_Int32); - if (ins->isFloatArray()) - JS_ASSERT(ins->value()->type() == MIRType_Double); - else + if (ins->isFloatArray()) { + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT32, ins->value()->type() == MIRType_Float32); + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT64, ins->value()->type() == MIRType_Double); + } else { JS_ASSERT(ins->value()->type() == MIRType_Int32); + } LUse elements = useRegister(ins->elements()); LAllocation index = useRegisterOrConstant(ins->index()); @@ -122,10 +124,12 @@ LIRGeneratorX64::visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *in JS_ASSERT(ins->index()->type() == MIRType_Int32); JS_ASSERT(ins->length()->type() == MIRType_Int32); - if (ins->isFloatArray()) - JS_ASSERT(ins->value()->type() == MIRType_Double); - else + if (ins->isFloatArray()) { + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT32, ins->value()->type() == MIRType_Float32); + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT64, ins->value()->type() == MIRType_Double); + } else { JS_ASSERT(ins->value()->type() == MIRType_Int32); + } LUse elements = useRegister(ins->elements()); LAllocation length = useAnyOrConstant(ins->length()); diff --git a/js/src/jit/x64/Lowering-x64.h b/js/src/jit/x64/Lowering-x64.h index 9dcd6950385..339087c0e24 100644 --- a/js/src/jit/x64/Lowering-x64.h +++ b/js/src/jit/x64/Lowering-x64.h @@ -44,6 +44,10 @@ class LIRGeneratorX64 : public LIRGeneratorX86Shared bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins); bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins); bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins); + + static bool allowFloat32Optimizations() { + return true; + } }; typedef LIRGeneratorX64 LIRGeneratorSpecific; diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index 69ec12e14c7..9ca70cbdf48 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -962,10 +962,29 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared cvtsi2sd(operand.valueReg(), dest); } + void boolValueToFloat32(const ValueOperand &operand, const FloatRegister &dest) { + cvtsi2ss(operand.valueReg(), dest); + } + void int32ValueToFloat32(const ValueOperand &operand, const FloatRegister &dest) { + cvtsi2ss(operand.valueReg(), dest); + } + void loadConstantDouble(double d, const FloatRegister &dest); + void loadConstantFloat32(float f, const FloatRegister &dest) { + union FloatPun { + uint32_t u; + float f; + } pun; + pun.f = f; + mov(ImmWord(pun.u), ScratchReg); + movq(ScratchReg, dest); + } void loadStaticDouble(const double *dp, const FloatRegister &dest) { loadConstantDouble(*dp, dest); } + void loadStaticFloat32(const float *fp, const FloatRegister &dest) { + loadConstantFloat32(*fp, dest); + } void branchTruncateDouble(const FloatRegister &src, const Register &dest, Label *fail) { const uint64_t IndefiniteIntegerValue = 0x8000000000000000; diff --git a/js/src/jit/x86/Assembler-x86.cpp b/js/src/jit/x86/Assembler-x86.cpp index bc947649995..d2039368938 100644 --- a/js/src/jit/x86/Assembler-x86.cpp +++ b/js/src/jit/x86/Assembler-x86.cpp @@ -25,6 +25,7 @@ ABIArgGenerator::next(MIRType type) case MIRType_Pointer: stackOffset_ += sizeof(uint32_t); break; + case MIRType_Float32: // Float32 moves are actually double moves case MIRType_Double: stackOffset_ += sizeof(uint64_t); break; diff --git a/js/src/jit/x86/Assembler-x86.h b/js/src/jit/x86/Assembler-x86.h index 8932e7a211c..03c8169f5d2 100644 --- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -251,6 +251,7 @@ class Assembler : public AssemblerX86Shared using AssemblerX86Shared::j; using AssemblerX86Shared::jmp; using AssemblerX86Shared::movsd; + using AssemblerX86Shared::movss; using AssemblerX86Shared::retarget; using AssemblerX86Shared::cmpl; using AssemblerX86Shared::call; @@ -444,6 +445,11 @@ class Assembler : public AssemblerX86Shared masm.movsd_mr((const void *)dp, dest.code()); } + void movss(const float *dp, const FloatRegister &dest) { + JS_ASSERT(HasSSE2()); + masm.movss_mr((const void *)dp, dest.code()); + } + // Move a 32-bit immediate into a register where the immediate can be // patched. CodeOffsetLabel movlWithPatch(Imm32 imm, Register dest) { @@ -461,6 +467,11 @@ class Assembler : public AssemblerX86Shared masm.movsd_mr(addr, dest.code()); return masm.currentOffset(); } + CodeOffsetLabel movssWithPatch(void *addr, FloatRegister dest) { + JS_ASSERT(HasSSE2()); + masm.movss_mr(addr, dest.code()); + return masm.currentOffset(); + } // Store to *addr where addr can be patched CodeOffsetLabel movlWithPatch(Register src, void *addr) { @@ -472,6 +483,11 @@ class Assembler : public AssemblerX86Shared masm.movsd_rm(dest.code(), addr); return masm.currentOffset(); } + CodeOffsetLabel movssWithPatch(FloatRegister dest, void *addr) { + JS_ASSERT(HasSSE2()); + masm.movss_rm(dest.code(), addr); + return masm.currentOffset(); + } // Load from *(base + disp32) where disp32 can be patched. CodeOffsetLabel movsblWithPatch(Address src, Register dest) { diff --git a/js/src/jit/x86/CodeGenerator-x86.cpp b/js/src/jit/x86/CodeGenerator-x86.cpp index 5d37ceae193..06d250f5536 100644 --- a/js/src/jit/x86/CodeGenerator-x86.cpp +++ b/js/src/jit/x86/CodeGenerator-x86.cpp @@ -464,10 +464,10 @@ CodeGeneratorX86::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic Address srcAddr(ptr, (int32_t) mir->base()); if (vt == ArrayBufferView::TYPE_FLOAT32) { + JS_ASSERT(mir->type() == MIRType_Float32); FloatRegister dest = ToFloatRegister(out); masm.movssWithPatch(srcAddr, dest); - masm.cvtss2sd(dest, dest); - masm.canonicalizeDouble(dest); + masm.canonicalizeFloat(dest); if (ool) masm.bind(ool->rejoin()); return true; @@ -562,8 +562,8 @@ CodeGeneratorX86::storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllo const T &dstAddr) { if (vt == ArrayBufferView::TYPE_FLOAT32) { - masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg); uint32_t before = masm.size(); + masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg); masm.movssWithPatch(ScratchFloatReg, dstAddr); uint32_t after = masm.size(); return gen->noteHeapAccess(AsmJSHeapAccess(before, after)); @@ -589,8 +589,8 @@ CodeGeneratorX86::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStati Address dstAddr(ptr, (int32_t) mir->base()); if (vt == ArrayBufferView::TYPE_FLOAT32) { - masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg); - masm.movssWithPatch(ScratchFloatReg, dstAddr); + JS_ASSERT(mir->value()->type() == MIRType_Float32); + masm.movssWithPatch(ToFloatRegister(value), dstAddr); masm.bind(&rejoin); return true; } diff --git a/js/src/jit/x86/Lowering-x86.cpp b/js/src/jit/x86/Lowering-x86.cpp index 5e816792eda..445e608affc 100644 --- a/js/src/jit/x86/Lowering-x86.cpp +++ b/js/src/jit/x86/Lowering-x86.cpp @@ -166,10 +166,12 @@ LIRGeneratorX86::visitStoreTypedArrayElement(MStoreTypedArrayElement *ins) JS_ASSERT(ins->elements()->type() == MIRType_Elements); JS_ASSERT(ins->index()->type() == MIRType_Int32); - if (ins->isFloatArray()) - JS_ASSERT(ins->value()->type() == MIRType_Double); - else + if (ins->isFloatArray()) { + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT64, ins->value()->type() == MIRType_Double); + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT32, ins->value()->type() == MIRType_Float32); + } else { JS_ASSERT(ins->value()->type() == MIRType_Int32); + } LUse elements = useRegister(ins->elements()); LAllocation index = useRegisterOrConstant(ins->index()); @@ -190,10 +192,12 @@ LIRGeneratorX86::visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *in JS_ASSERT(ins->index()->type() == MIRType_Int32); JS_ASSERT(ins->length()->type() == MIRType_Int32); - if (ins->isFloatArray()) - JS_ASSERT(ins->value()->type() == MIRType_Double); - else + if (ins->isFloatArray()) { + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT64, ins->value()->type() == MIRType_Double); + JS_ASSERT_IF(ins->arrayType() == ScalarTypeRepresentation::TYPE_FLOAT32, ins->value()->type() == MIRType_Float32); + } else { JS_ASSERT(ins->value()->type() == MIRType_Int32); + } LUse elements = useRegister(ins->elements()); LAllocation length = useAnyOrConstant(ins->length()); diff --git a/js/src/jit/x86/Lowering-x86.h b/js/src/jit/x86/Lowering-x86.h index cc2ea1f7c8c..11658badcf0 100644 --- a/js/src/jit/x86/Lowering-x86.h +++ b/js/src/jit/x86/Lowering-x86.h @@ -56,6 +56,10 @@ class LIRGeneratorX86 : public LIRGeneratorX86Shared static bool allowStaticTypedArrayAccesses() { return true; } + + static bool allowFloat32Optimizations() { + return true; + } }; typedef LIRGeneratorX86 LIRGeneratorSpecific; diff --git a/js/src/jit/x86/MacroAssembler-x86.cpp b/js/src/jit/x86/MacroAssembler-x86.cpp index 0508043a9cc..ccb96c20341 100644 --- a/js/src/jit/x86/MacroAssembler-x86.cpp +++ b/js/src/jit/x86/MacroAssembler-x86.cpp @@ -47,6 +47,34 @@ MacroAssemblerX86::loadConstantDouble(double d, const FloatRegister &dest) dbl.uses.setPrev(masm.size()); } +void +MacroAssemblerX86::loadConstantFloat32(float f, const FloatRegister &dest) +{ + // Contrarily to loadConstantDouble, this one doesn't have any maybeInlineFloat, + // but that might be interesting to do it in the future. + if (!floatMap_.initialized()) { + enoughMemory_ &= floatMap_.init(); + if (!enoughMemory_) + return; + } + size_t floatIndex; + FloatMap::AddPtr p = floatMap_.lookupForAdd(f); + if (p) { + floatIndex = p->value; + } else { + floatIndex = floats_.length(); + enoughMemory_ &= floats_.append(Float(f)); + enoughMemory_ &= floatMap_.add(p, f, floatIndex); + if (!enoughMemory_) + return; + } + Float &flt = floats_[floatIndex]; + JS_ASSERT(!flt.uses.bound()); + + masm.movss_mr(reinterpret_cast(flt.uses.prev()), dest.code()); + flt.uses.setPrev(masm.size()); +} + void MacroAssemblerX86::loadStaticDouble(const double *dp, const FloatRegister &dest) { if (maybeInlineDouble(*dp, dest)) @@ -56,10 +84,16 @@ MacroAssemblerX86::loadStaticDouble(const double *dp, const FloatRegister &dest) movsd(dp, dest); } +void +MacroAssemblerX86::loadStaticFloat32(const float *fp, const FloatRegister &dest) { + // x86 can just load from any old immediate address. + movss(fp, dest); +} + void MacroAssemblerX86::finish() { - if (doubles_.empty()) + if (doubles_.empty() && floats_.empty()) return; masm.align(sizeof(double)); @@ -70,6 +104,13 @@ MacroAssemblerX86::finish() if (!enoughMemory_) return; } + for (size_t i = 0; i < floats_.length(); i++) { + CodeLabel cl(floats_[i].uses); + writeFloatConstant(floats_[i].value, cl.src()); + enoughMemory_ &= addCodeLabel(cl); + if (!enoughMemory_) + return; + } } void diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index c5a8e9295a0..080a6b13b72 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -33,9 +33,17 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared Double(double value) : value(value) {} }; Vector doubles_; + struct Float { + float value; + AbsoluteLabel uses; + Float(float value) : value(value) {} + }; + Vector floats_; typedef HashMap, SystemAllocPolicy> DoubleMap; DoubleMap doubleMap_; + typedef HashMap, SystemAllocPolicy> FloatMap; + FloatMap floatMap_; protected: MoveResolver moveResolver_; @@ -816,12 +824,20 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void boolValueToDouble(const ValueOperand &operand, const FloatRegister &dest) { cvtsi2sd(operand.payloadReg(), dest); } + void boolValueToFloat32(const ValueOperand &operand, const FloatRegister &dest) { + cvtsi2ss(operand.payloadReg(), dest); + } void int32ValueToDouble(const ValueOperand &operand, const FloatRegister &dest) { cvtsi2sd(operand.payloadReg(), dest); } + void int32ValueToFloat32(const ValueOperand &operand, const FloatRegister &dest) { + cvtsi2ss(operand.payloadReg(), dest); + } void loadConstantDouble(double d, const FloatRegister &dest); + void loadConstantFloat32(float f, const FloatRegister &dest); void loadStaticDouble(const double *dp, const FloatRegister &dest); + void loadStaticFloat32(const float *dp, const FloatRegister &dest); void branchTruncateDouble(const FloatRegister &src, const Register &dest, Label *fail) { const uint32_t IndefiniteIntegerValue = 0x80000000; @@ -829,6 +845,12 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmpl(dest, Imm32(IndefiniteIntegerValue)); j(Assembler::Equal, fail); } + void branchTruncateFloat32(const FloatRegister &src, const Register &dest, Label *fail) { + const uint32_t IndefiniteIntegerValue = 0x80000000; + cvttss2si(src, dest); + cmpl(dest, Imm32(IndefiniteIntegerValue)); + j(Assembler::Equal, fail); + } Condition testInt32Truthy(bool truthy, const ValueOperand &operand) { testl(operand.payloadReg(), operand.payloadReg()); @@ -909,6 +931,20 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared addsd(Operand(&NegativeOne), dest); } + // Note: this function clobbers the source register. + void convertUInt32ToFloat32(const Register &src, const FloatRegister &dest) { + // src is [0, 2^32-1] + subl(Imm32(0x80000000), src); + + // Do it the GCC way + cvtsi2ss(src, dest); + + // dest is now a double with the int range. + // correct the double value by adding 0x80000000. + static const float NegativeOne = 2147483648.f; + addss(Operand(&NegativeOne), dest); + } + void inc64(AbsoluteAddress dest) { addl(Imm32(1), Operand(dest)); Label noOverflow;