diff --git a/hphp/compiler/analysis/emitter.cpp b/hphp/compiler/analysis/emitter.cpp index 491ceb2d4..6fd7e9808 100755 --- a/hphp/compiler/analysis/emitter.cpp +++ b/hphp/compiler/analysis/emitter.cpp @@ -3171,6 +3171,20 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { e.Strlen(); return true; } + } else if (call->isCallToFunction("floor")) { + if (params && params->getCount() == 1) { + visit((*params)[0]); + emitConvertToCell(e); + e.Floor(); + return true; + } + } else if (call->isCallToFunction("ceil")) { + if (params && params->getCount() == 1) { + visit((*params)[0]); + emitConvertToCell(e); + e.Ceil(); + return true; + } } else if (call->isCallToFunction("define")) { if (params && params->getCount() == 2) { ExpressionPtr p0 = (*params)[0]; diff --git a/hphp/doc/bytecode.specification b/hphp/doc/bytecode.specification index 55f8c451a..fd2f83abe 100644 --- a/hphp/doc/bytecode.specification +++ b/hphp/doc/bytecode.specification @@ -1058,6 +1058,16 @@ Shr [C C] -> [C:Int] Shift right (>>). Pushes ((int)$2 >> (int)$1) onto the stack. This instruction never throws a fatal error. +Floor [C] -> [C:Dbl] + + Round $1 to nearest integer value not greater than $1. Converts $1 to + numeric as appropriate and then takes floor of resulting numeric value. + +Ceil [C] -> [C:Dbl] + + Round $1 to nearest integer value not less than $1. Converts $1 to numeric + as appropriate and then takes ceil of resulting numeric value. + CastBool [C] -> [C:Bool] Cast to boolean ((bool),(boolean)). Pushes (bool)$1 onto the stack. diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index df8c1f4df..bc9a2805a 100755 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -477,6 +477,8 @@ D:Int = OpBitOr S0:Int S1:Int D:Int = OpBitXor S0:Int S1:Int D:Int = OpShl S0:Int S1:Int D:Int = OpShr S0:Int S1:Int +D:Dbl = OpFloor S0:Dbl +D:Dbl = OpCeil S0:Dbl Integer/boolean arithmetic. Performs the operation described by the opcode name on S0 and S1, and puts the result in D. @@ -491,6 +493,10 @@ D:Int = OpShr S0:Int S1:Int Note that OpShr is an arithmetic right shift. + OpFloor and OpCeil will return an integral value not greater, or not less + than their input respectively. Their use requires SSE 4.1, availability + should be checked before they are emitted. + D:Bool = OpLogicXor S0:Bool S1:Bool Logical XOR of the two sources. (Note that && and || do not have diff --git a/hphp/runtime/base/execution_context.h b/hphp/runtime/base/execution_context.h index 32c0dcd34..a1ff0d8f8 100644 --- a/hphp/runtime/base/execution_context.h +++ b/hphp/runtime/base/execution_context.h @@ -485,6 +485,7 @@ private: Offset offset, Ref* r1, TypedValue* val, TypedValue* key); void jmpSurpriseCheck(Offset o); template void jmpOpImpl(PC& pc); + template void roundOpImpl(Op op); #define O(name, imm, pusph, pop, flags) \ void iop##name(PC& pc); OPCODES diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index 54fc88d4e..382b9e9a7 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -6825,6 +6825,24 @@ inline void OPTBLD_INLINE VMExecutionContext::iopContHandle(PC& pc) { throw exn.asObjRef(); } +template +inline void OPTBLD_INLINE VMExecutionContext::roundOpImpl(Op op) { + TypedValue* val = m_stack.topTV(); + + tvCastToDoubleInPlace(val); + val->m_data.dbl = op(val->m_data.dbl); +} + +inline void OPTBLD_INLINE VMExecutionContext::iopFloor(PC& pc) { + NEXT(); + roundOpImpl(floor); +} + +inline void OPTBLD_INLINE VMExecutionContext::iopCeil(PC& pc) { + NEXT(); + roundOpImpl(ceil); +} + inline void OPTBLD_INLINE VMExecutionContext::iopStrlen(PC& pc) { NEXT(); TypedValue* subj = m_stack.topTV(); diff --git a/hphp/runtime/vm/hhbc.h b/hphp/runtime/vm/hhbc.h index e8aaf29df..331068da7 100644 --- a/hphp/runtime/vm/hhbc.h +++ b/hphp/runtime/vm/hhbc.h @@ -570,6 +570,8 @@ enum SetOpOp { O(Strlen, NA, ONE(CV), ONE(CV), NF) \ O(IncStat, TWO(IVA,IVA), NOV, NOV, NF) \ O(ArrayIdx, NA, THREE(CV,CV,CV), ONE(CV), NF) \ + O(Floor, NA, ONE(CV), ONE(CV), NF) \ + O(Ceil, NA, ONE(CV), ONE(CV), NF) \ O(HighInvalid, NA, NOV, NOV, NF) \ enum class Op : uint8_t { diff --git a/hphp/runtime/vm/jit/code-gen.cpp b/hphp/runtime/vm/jit/code-gen.cpp index 00dbec6f9..f04023daa 100755 --- a/hphp/runtime/vm/jit/code-gen.cpp +++ b/hphp/runtime/vm/jit/code-gen.cpp @@ -1385,6 +1385,26 @@ bool CodeGenerator::emitDec(SSATmp* dst, SSATmp* src1, SSATmp* src2) { return emitIncDecHelper(dst, src1, src2, &Asm::decq); } +void CodeGenerator::cgRoundCommon(IRInstruction* inst, RoundDirection dir) { + auto dst = inst->dst(); + auto src = inst->src(0); + + auto dstReg = m_regs[dst].reg(); + auto inReg = prepXMMReg(src, m_as, m_regs, rCgXMM0); + auto outReg = dstReg.isXMM() ? dstReg : PhysReg(rCgXMM1); + + m_as. roundsd (dir, inReg, outReg); + emitMovRegReg(m_as, outReg, dstReg); +} + +void CodeGenerator::cgOpFloor(IRInstruction* inst) { + cgRoundCommon(inst, RoundDirection::floor); +} + +void CodeGenerator::cgOpCeil(IRInstruction* inst) { + cgRoundCommon(inst, RoundDirection::ceil); +} + void CodeGenerator::cgOpAdd(IRInstruction* inst) { SSATmp* dst = inst->dst(); SSATmp* src1 = inst->src(0); diff --git a/hphp/runtime/vm/jit/code-gen.h b/hphp/runtime/vm/jit/code-gen.h index 41bfcb312..5720dae22 100644 --- a/hphp/runtime/vm/jit/code-gen.h +++ b/hphp/runtime/vm/jit/code-gen.h @@ -206,6 +206,8 @@ private: enum Commutativity { Commutative, NonCommutative }; + void cgRoundCommon(IRInstruction* inst, RoundDirection dir); + template void cgBinaryOp(IRInstruction*, void (Asm::*intImm)(Immed, RegType), diff --git a/hphp/runtime/vm/jit/hhbc-translator.cpp b/hphp/runtime/vm/jit/hhbc-translator.cpp index d15a0b387..b082555bc 100755 --- a/hphp/runtime/vm/jit/hhbc-translator.cpp +++ b/hphp/runtime/vm/jit/hhbc-translator.cpp @@ -15,6 +15,7 @@ */ #include "hphp/runtime/vm/jit/hhbc-translator.h" +#include "folly/CpuId.h" #include "hphp/util/trace.h" #include "hphp/runtime/ext/ext_closure.h" #include "hphp/runtime/ext/ext_continuation.h" @@ -3319,6 +3320,30 @@ void HhbcTranslator::emitNot() { gen(DecRef, src); } +void HhbcTranslator::emitFloor() { + // need SSE 4.1 support to use roundsd + if (!folly::CpuId().sse41()) { + PUNT(Floor); + } + + auto val = popC(); + auto dblVal = gen(ConvCellToDbl, val); + gen(DecRef, val); + push(gen(OpFloor, dblVal)); +} + +void HhbcTranslator::emitCeil() { + // need SSE 4.1 support to use roundsd + if (!folly::CpuId().sse41()) { + PUNT(Ceil); + } + + auto val = popC(); + auto dblVal = gen(ConvCellToDbl, val); + gen(DecRef, val); + push(gen(OpCeil, dblVal)); +} + #define BINOP(Opp) \ void HhbcTranslator::emit ## Opp() { \ emitBinaryArith(Op ## Opp); \ diff --git a/hphp/runtime/vm/jit/hhbc-translator.h b/hphp/runtime/vm/jit/hhbc-translator.h index e53500429..03981753a 100755 --- a/hphp/runtime/vm/jit/hhbc-translator.h +++ b/hphp/runtime/vm/jit/hhbc-translator.h @@ -329,6 +329,9 @@ struct HhbcTranslator { void emitRetC(bool freeInline); void emitRetV(bool freeInline); + void emitFloor(); + void emitCeil(); + // binary arithmetic ops void emitAdd(); void emitSub(); diff --git a/hphp/runtime/vm/jit/ir-translator.cpp b/hphp/runtime/vm/jit/ir-translator.cpp index 817bc0a3d..9c42f1393 100644 --- a/hphp/runtime/vm/jit/ir-translator.cpp +++ b/hphp/runtime/vm/jit/ir-translator.cpp @@ -439,6 +439,16 @@ IRTranslator::translateAddElemC(const NormalizedInstruction& i) { HHIR_EMIT(AddElemC); } +void +IRTranslator::translateFloor(const NormalizedInstruction& i) { + HHIR_EMIT(Floor); +} + +void +IRTranslator::translateCeil(const NormalizedInstruction& i) { + HHIR_EMIT(Ceil); +} + void IRTranslator::translateAddNewElemC(const NormalizedInstruction& i) { assert(i.inputs.size() == 2); diff --git a/hphp/runtime/vm/jit/ir.h b/hphp/runtime/vm/jit/ir.h index 3bce1ce14..d80904128 100755 --- a/hphp/runtime/vm/jit/ir.h +++ b/hphp/runtime/vm/jit/ir.h @@ -272,6 +272,8 @@ O(OpEq, D(Bool), S(Gen) S(Gen), C|N) \ O(OpNeq, D(Bool), S(Gen) S(Gen), C|N) \ O(OpSame, D(Bool), S(Gen) S(Gen), C|N) \ O(OpNSame, D(Bool), S(Gen) S(Gen), C|N) \ +O(OpFloor, D(Dbl), S(Dbl), C) \ +O(OpCeil, D(Dbl), S(Dbl), C) \ O(InstanceOfBitmask, D(Bool), S(Cls) CStr, C) \ O(NInstanceOfBitmask, D(Bool), S(Cls) CStr, C) \ O(IsType, D(Bool), S(Cell), C) \ diff --git a/hphp/runtime/vm/jit/simplifier.cpp b/hphp/runtime/vm/jit/simplifier.cpp index ff986bce8..161531e8e 100644 --- a/hphp/runtime/vm/jit/simplifier.cpp +++ b/hphp/runtime/vm/jit/simplifier.cpp @@ -348,6 +348,8 @@ SSATmp* Simplifier::simplify(IRInstruction* inst) { case ConvCellToBool:return simplifyConvCellToBool(inst); case ConvCellToInt: return simplifyConvCellToInt(inst); case ConvCellToDbl: return simplifyConvCellToDbl(inst); + case OpFloor: return simplifyFloor(inst); + case OpCeil: return simplifyCeil(inst); case Unbox: return simplifyUnbox(inst); case UnboxPtr: return simplifyUnboxPtr(inst); case IsType: @@ -1425,6 +1427,7 @@ SSATmp* Simplifier::simplifyConvDblToBool(IRInstruction* inst) { if (src->isConst()) { return cns(bool(src->getValDbl())); } + return nullptr; } @@ -1608,6 +1611,30 @@ SSATmp* Simplifier::simplifyConvCellToDbl(IRInstruction* inst) { return nullptr; } +template +SSATmp* Simplifier::simplifyRoundCommon(IRInstruction* inst, Oper op) { + auto const src = inst->src(0); + + if (src->isConst()) { + return cns(op(src->getValDbl())); + } + + auto srcInst = src->inst(); + if (srcInst->op() == ConvIntToDbl || srcInst->op() == ConvBoolToDbl) { + return src; + } + + return nullptr; +} + +SSATmp* Simplifier::simplifyFloor(IRInstruction* inst) { + return simplifyRoundCommon(inst, floor); +} + +SSATmp* Simplifier::simplifyCeil(IRInstruction* inst) { + return simplifyRoundCommon(inst, ceil); +} + SSATmp* Simplifier::simplifyLdClsPropAddr(IRInstruction* inst) { SSATmp* propName = inst->src(1); if (!propName->isConst()) return nullptr; diff --git a/hphp/runtime/vm/jit/simplifier.h b/hphp/runtime/vm/jit/simplifier.h index 30d58b0f4..2d564a6c9 100644 --- a/hphp/runtime/vm/jit/simplifier.h +++ b/hphp/runtime/vm/jit/simplifier.h @@ -107,6 +107,8 @@ private: SSATmp* simplifyConvCellToBool(IRInstruction*); SSATmp* simplifyConvCellToInt(IRInstruction*); SSATmp* simplifyConvCellToDbl(IRInstruction*); + SSATmp* simplifyFloor(IRInstruction*); + SSATmp* simplifyCeil(IRInstruction*); SSATmp* simplifyUnbox(IRInstruction*); SSATmp* simplifyUnboxPtr(IRInstruction*); SSATmp* simplifyCheckInit(IRInstruction* inst); @@ -141,6 +143,7 @@ private: template SSATmp* simplifyShift(SSATmp* src1, SSATmp* src2, Oper op); + template SSATmp* simplifyRoundCommon(IRInstruction*, Oper); private: // tracebuilder forwarders template SSATmp* cns(Args&&...); diff --git a/hphp/runtime/vm/jit/translator-instrs.h b/hphp/runtime/vm/jit/translator-instrs.h index da2839a9f..6175f1aed 100644 --- a/hphp/runtime/vm/jit/translator-instrs.h +++ b/hphp/runtime/vm/jit/translator-instrs.h @@ -167,7 +167,9 @@ CASE(DecodeCufIter) \ CASE(Shl) \ CASE(Shr) \ - CASE(Div) + CASE(Div) \ + CASE(Floor) \ + CASE(Ceil) \ // These are instruction-like functions which cover more than one // opcode. diff --git a/hphp/runtime/vm/jit/translator.cpp b/hphp/runtime/vm/jit/translator.cpp index f505d9c68..5995e64a6 100755 --- a/hphp/runtime/vm/jit/translator.cpp +++ b/hphp/runtime/vm/jit/translator.cpp @@ -1261,6 +1261,8 @@ static const struct { { OpStrlen, {Stack1, Stack1, OutStrlen, 0 }}, { OpIncStat, {None, None, OutNone, 0 }}, { OpArrayIdx, {StackTop3, Stack1, OutUnknown, -2 }}, + { OpFloor, {Stack1, Stack1, OutDouble, 0 }}, + { OpCeil, {Stack1, Stack1, OutDouble, 0 }}, /*** 14. Continuation instructions ***/ diff --git a/hphp/test/quick/rounding.php b/hphp/test/quick/rounding.php new file mode 100644 index 000000000..7592b8f15 --- /dev/null +++ b/hphp/test/quick/rounding.php @@ -0,0 +1,56 @@ +