Bug 1297749 - Inline String.fromCodePoint in Ion. r=jandem
authorAndré Bargull <andre.bargull@gmail.com>
Wed, 05 Oct 2016 03:25:32 -0700
changeset 316794 d07da2290d3ea92f41132519f87d0b55524e7d2b
parent 316793 b8af68ee1064cf26ace9ef913408828491cb23c2
child 316795 632091ca724bd957156082a38e6b4853d777d889
push id32932
push userphilringnalda@gmail.com
push dateFri, 07 Oct 2016 03:24:25 +0000
treeherderautoland@7affb66131bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1297749
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1297749 - Inline String.fromCodePoint in Ion. r=jandem
js/src/jit-test/tests/ion/testStringFromCodePoint.js
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/InlinableNatives.h
js/src/jit/IonBuilder.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
js/src/jsstr.cpp
js/src/jsstr.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/testStringFromCodePoint.js
@@ -0,0 +1,32 @@
+setJitCompilerOption("ion.warmup.trigger", 20);
+
+function testBailout() {
+    function f(v, r) {
+        for (var i = 0; i < 50; ++i) {
+            // Ensure DCE and LICM don't eliminate calls to fromCodePoint in
+            // case the input argument is not a valid code point.
+            if (i === 0) {
+                r();
+            }
+            String.fromCodePoint(v);
+            String.fromCodePoint(v);
+            String.fromCodePoint(v);
+        }
+    }
+
+    var result = [];
+    function r() {
+        result.push("ok");
+    }
+
+    do {
+        result.length = 0;
+        try {
+            f(0, r);
+            f(0, r);
+            f(0x10ffff + 1, r);
+        } catch (e) {}
+        assertEq(result.length, 3);
+    } while (!inIon());
+}
+testBailout();
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7671,16 +7671,45 @@ CodeGenerator::visitFromCharCode(LFromCh
                   ool->entry());
 
     masm.movePtr(ImmPtr(&GetJitContext()->runtime->staticStrings().unitStaticTable), output);
     masm.loadPtr(BaseIndex(output, code, ScalePointer), output);
 
     masm.bind(ool->rejoin());
 }
 
+typedef JSString* (*StringFromCodePointFn)(JSContext*, int32_t);
+static const VMFunction StringFromCodePointInfo =
+    FunctionInfo<StringFromCodePointFn>(jit::StringFromCodePoint, "StringFromCodePoint");
+
+void
+CodeGenerator::visitFromCodePoint(LFromCodePoint* lir)
+{
+    Register codePoint = ToRegister(lir->codePoint());
+    Register output = ToRegister(lir->output());
+    LSnapshot* snapshot = lir->snapshot();
+
+    OutOfLineCode* ool = oolCallVM(StringFromCodePointInfo, lir, ArgList(codePoint),
+                                   StoreRegisterTo(output));
+
+    // Use a bailout if the input is not a valid code point, because
+    // MFromCodePoint is movable and it'd be observable when a moved
+    // fromCodePoint throws an exception before its actual call site.
+    bailoutCmp32(Assembler::Above, codePoint, Imm32(unicode::NonBMPMax), snapshot);
+
+    // OOL path if code point >= UNIT_STATIC_LIMIT.
+    masm.branch32(Assembler::AboveOrEqual, codePoint, Imm32(StaticStrings::UNIT_STATIC_LIMIT),
+                  ool->entry());
+
+    masm.movePtr(ImmPtr(&GetJitContext()->runtime->staticStrings().unitStaticTable), output);
+    masm.loadPtr(BaseIndex(output, codePoint, ScalePointer), output);
+
+    masm.bind(ool->rejoin());
+}
+
 void
 CodeGenerator::visitSinCos(LSinCos *lir)
 {
     Register temp = ToRegister(lir->temp());
     Register params = ToRegister(lir->temp2());
     FloatRegister input = ToFloatRegister(lir->input());
     FloatRegister outputSin = ToFloatRegister(lir->outputSin());
     FloatRegister outputCos = ToFloatRegister(lir->outputCos());
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -273,16 +273,17 @@ class CodeGenerator final : public CodeG
     void visitIsNullOrLikeUndefinedV(LIsNullOrLikeUndefinedV* lir);
     void visitIsNullOrLikeUndefinedT(LIsNullOrLikeUndefinedT* lir);
     void visitIsNullOrLikeUndefinedAndBranchV(LIsNullOrLikeUndefinedAndBranchV* lir);
     void visitIsNullOrLikeUndefinedAndBranchT(LIsNullOrLikeUndefinedAndBranchT* lir);
     void emitConcat(LInstruction* lir, Register lhs, Register rhs, Register output);
     void visitConcat(LConcat* lir);
     void visitCharCodeAt(LCharCodeAt* lir);
     void visitFromCharCode(LFromCharCode* lir);
+    void visitFromCodePoint(LFromCodePoint* lir);
     void visitSinCos(LSinCos *lir);
     void visitStringSplit(LStringSplit* lir);
     void visitFunctionEnvironment(LFunctionEnvironment* lir);
     void visitCallGetProperty(LCallGetProperty* lir);
     void visitCallGetElement(LCallGetElement* lir);
     void visitCallSetElement(LCallSetElement* lir);
     void visitCallInitElementArray(LCallInitElementArray* lir);
     void visitThrow(LThrow* lir);
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -70,16 +70,17 @@
     _(IsRegExpObject)               \
     _(RegExpPrototypeOptimizable)   \
     _(RegExpInstanceOptimizable)    \
     _(GetFirstDollarIndex)          \
                                     \
     _(String)                       \
     _(StringCharCodeAt)             \
     _(StringFromCharCode)           \
+    _(StringFromCodePoint)          \
     _(StringCharAt)                 \
                                     \
     _(IntrinsicStringReplaceString) \
     _(IntrinsicStringSplitString)   \
                                     \
     _(ObjectCreate)                 \
                                     \
     _(SimdInt32x4)                  \
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -845,16 +845,17 @@ class IonBuilder
     InliningStatus inlineMathFRound(CallInfo& callInfo);
     InliningStatus inlineMathFunction(CallInfo& callInfo, MMathFunction::Function function);
 
     // String natives.
     InliningStatus inlineStringObject(CallInfo& callInfo);
     InliningStatus inlineStrCharCodeAt(CallInfo& callInfo);
     InliningStatus inlineConstantCharCodeAt(CallInfo& callInfo);
     InliningStatus inlineStrFromCharCode(CallInfo& callInfo);
+    InliningStatus inlineStrFromCodePoint(CallInfo& callInfo);
     InliningStatus inlineStrCharAt(CallInfo& callInfo);
 
     // String intrinsics.
     InliningStatus inlineStringReplaceString(CallInfo& callInfo);
     InliningStatus inlineConstantStringSplitString(CallInfo& callInfo);
     InliningStatus inlineStringSplitString(CallInfo& callInfo);
 
     // RegExp intrinsics.
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1896,16 +1896,29 @@ LIRGenerator::visitFromCharCode(MFromCha
     MOZ_ASSERT(code->type() == MIRType::Int32);
 
     LFromCharCode* lir = new(alloc()) LFromCharCode(useRegister(code));
     define(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
+LIRGenerator::visitFromCodePoint(MFromCodePoint* ins)
+{
+    MDefinition* codePoint = ins->getOperand(0);
+
+    MOZ_ASSERT(codePoint->type() == MIRType::Int32);
+
+    LFromCodePoint* lir = new(alloc()) LFromCodePoint(useRegister(codePoint));
+    assignSnapshot(lir, Bailout_BoundsCheck);
+    define(lir, ins);
+    assignSafepoint(lir, ins);
+}
+
+void
 LIRGenerator::visitStart(MStart* start)
 {
     LStart* lir = new(alloc()) LStart;
 
     // Create a snapshot that captures the initial state of the function.
     assignSnapshot(lir, Bailout_ArgumentCheck);
     if (start->block()->graph().entryBlock() == start->block())
         lirGraph_.setEntrySnapshot(lir->snapshot());
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -143,16 +143,17 @@ class LIRGenerator : public LIRGenerator
     void visitAdd(MAdd* ins);
     void visitSub(MSub* ins);
     void visitMul(MMul* ins);
     void visitDiv(MDiv* ins);
     void visitMod(MMod* ins);
     void visitConcat(MConcat* ins);
     void visitCharCodeAt(MCharCodeAt* ins);
     void visitFromCharCode(MFromCharCode* ins);
+    void visitFromCodePoint(MFromCodePoint* ins);
     void visitSinCos(MSinCos *ins);
     void visitStringSplit(MStringSplit* ins);
     void visitStart(MStart* start);
     void visitOsrEntry(MOsrEntry* entry);
     void visitNop(MNop* nop);
     void visitLimitedTruncate(MLimitedTruncate* nop);
     void visitOsrValue(MOsrValue* value);
     void visitOsrEnvironmentChain(MOsrEnvironmentChain* object);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -197,16 +197,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
 
       // String natives.
       case InlinableNative::String:
         return inlineStringObject(callInfo);
       case InlinableNative::StringCharCodeAt:
         return inlineStrCharCodeAt(callInfo);
       case InlinableNative::StringFromCharCode:
         return inlineStrFromCharCode(callInfo);
+      case InlinableNative::StringFromCodePoint:
+        return inlineStrFromCodePoint(callInfo);
       case InlinableNative::StringCharAt:
         return inlineStrCharAt(callInfo);
 
       // String intrinsics.
       case InlinableNative::IntrinsicStringReplaceString:
         return inlineStringReplaceString(callInfo);
       case InlinableNative::IntrinsicStringSplitString:
         return inlineStringSplitString(callInfo);
@@ -1698,20 +1700,38 @@ IonBuilder::inlineStrFromCharCode(CallIn
 
     if (getInlineReturnType() != MIRType::String)
         return InliningStatus_NotInlined;
     if (callInfo.getArg(0)->type() != MIRType::Int32)
         return InliningStatus_NotInlined;
 
     callInfo.setImplicitlyUsedUnchecked();
 
-    MToInt32* charCode = MToInt32::New(alloc(), callInfo.getArg(0));
-    current->add(charCode);
-
-    MFromCharCode* string = MFromCharCode::New(alloc(), charCode);
+    MFromCharCode* string = MFromCharCode::New(alloc(), callInfo.getArg(0));
+    current->add(string);
+    current->push(string);
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
+IonBuilder::inlineStrFromCodePoint(CallInfo& callInfo)
+{
+    if (callInfo.argc() != 1 || callInfo.constructing()) {
+        trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
+        return InliningStatus_NotInlined;
+    }
+
+    if (getInlineReturnType() != MIRType::String)
+        return InliningStatus_NotInlined;
+    if (callInfo.getArg(0)->type() != MIRType::Int32)
+        return InliningStatus_NotInlined;
+
+    callInfo.setImplicitlyUsedUnchecked();
+
+    MFromCodePoint* string = MFromCodePoint::New(alloc(), callInfo.getArg(0));
     current->add(string);
     current->push(string);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
 IonBuilder::inlineStrCharAt(CallInfo& callInfo)
 {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -7322,16 +7322,43 @@ class MFromCharCode
     MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
     bool canRecoverOnBailout() const override {
         return true;
     }
 
     ALLOW_CLONE(MFromCharCode)
 };
 
+class MFromCodePoint
+  : public MUnaryInstruction,
+    public IntPolicy<0>::Data
+{
+    explicit MFromCodePoint(MDefinition* codePoint)
+      : MUnaryInstruction(codePoint)
+    {
+        setGuard(); // throws on invalid code point
+        setMovable();
+        setResultType(MIRType::String);
+    }
+
+  public:
+    INSTRUCTION_HEADER(FromCodePoint)
+    TRIVIAL_NEW_WRAPPERS
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::None();
+    }
+    bool congruentTo(const MDefinition* ins) const override {
+        return congruentIfOperandsEqual(ins);
+    }
+    bool possiblyCalls() const override {
+        return true;
+    }
+};
+
 class MSinCos
   : public MUnaryInstruction,
     public FloatingPointPolicy<0>::Data
 {
     const MathCache* cache_;
 
     MSinCos(MDefinition *input, const MathCache *cache) : MUnaryInstruction(input), cache_(cache)
     {
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -101,16 +101,17 @@ namespace jit {
     _(Add)                                                                  \
     _(Sub)                                                                  \
     _(Mul)                                                                  \
     _(Div)                                                                  \
     _(Mod)                                                                  \
     _(Concat)                                                               \
     _(CharCodeAt)                                                           \
     _(FromCharCode)                                                         \
+    _(FromCodePoint)                                                        \
     _(SinCos)                                                               \
     _(StringSplit)                                                          \
     _(Substr)                                                               \
     _(Return)                                                               \
     _(Throw)                                                                \
     _(Box)                                                                  \
     _(Unbox)                                                                \
     _(GuardObject)                                                          \
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -407,16 +407,26 @@ StringFromCharCode(JSContext* cx, int32_
     char16_t c = char16_t(code);
 
     if (StaticStrings::hasUnit(c))
         return cx->staticStrings().getUnit(c);
 
     return NewStringCopyN<CanGC>(cx, &c, 1);
 }
 
+JSString*
+StringFromCodePoint(JSContext* cx, int32_t codePoint)
+{
+    RootedValue rval(cx, Int32Value(codePoint));
+    if (!str_fromCodePoint_one_arg(cx, rval, &rval))
+        return nullptr;
+
+    return rval.toString();
+}
+
 bool
 SetProperty(JSContext* cx, HandleObject obj, HandlePropertyName name, HandleValue value,
             bool strict, jsbytecode* pc)
 {
     RootedId id(cx, NameToId(name));
 
     JSOp op = JSOp(*pc);
 
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -623,16 +623,17 @@ bool StringsEqual(JSContext* cx, HandleS
 MOZ_MUST_USE bool ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval);
 MOZ_MUST_USE bool ArrayPushDense(JSContext* cx, HandleObject obj, HandleValue v, uint32_t* length);
 MOZ_MUST_USE bool ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval);
 JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep);
 
 MOZ_MUST_USE bool
 CharCodeAt(JSContext* cx, HandleString str, int32_t index, uint32_t* code);
 JSFlatString* StringFromCharCode(JSContext* cx, int32_t code);
+JSString* StringFromCodePoint(JSContext* cx, int32_t codePoint);
 
 MOZ_MUST_USE bool
 SetProperty(JSContext* cx, HandleObject obj, HandlePropertyName name, HandleValue value,
             bool strict, jsbytecode* pc);
 
 MOZ_MUST_USE bool
 InterruptCheck(JSContext* cx);
 
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -4032,16 +4032,31 @@ class LFromCharCode : public LInstructio
         setOperand(0, code);
     }
 
     const LAllocation* code() {
         return this->getOperand(0);
     }
 };
 
+// Convert uint32 code point to a string.
+class LFromCodePoint : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(FromCodePoint)
+
+    explicit LFromCodePoint(const LAllocation& codePoint) {
+        setOperand(0, codePoint);
+    }
+
+    const LAllocation* codePoint() {
+        return this->getOperand(0);
+    }
+};
+
 // Calculates sincos(x) and returns two values (sin/cos).
 class LSinCos : public LCallInstructionHelper<2, 1, 2>
 {
   public:
     LIR_HEADER(SinCos)
 
     LSinCos(const LAllocation &input, const LDefinition &temp, const LDefinition &temp2)
     {
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -191,16 +191,17 @@
     _(DivPowTwoI)                   \
     _(ModI)                         \
     _(ModPowTwoI)                   \
     _(ModD)                         \
     _(BinaryV)                      \
     _(Concat)                       \
     _(CharCodeAt)                   \
     _(FromCharCode)                 \
+    _(FromCodePoint)                \
     _(SinCos)                       \
     _(StringSplit)                  \
     _(Int32ToDouble)                \
     _(Float32ToDouble)              \
     _(DoubleToFloat32)              \
     _(Int32ToFloat32)               \
     _(ValueToDouble)                \
     _(ValueToInt32)                 \
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2794,18 +2794,18 @@ ToCodePoint(JSContext* cx, HandleValue c
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_A_CODEPOINT, numStr);
         return false;
     }
 
     *codePoint = uint32_t(nextCP);
     return true;
 }
 
-static bool
-str_fromCodePoint_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval)
+bool
+js::str_fromCodePoint_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval)
 {
     // Steps 1-4 (omitted).
 
     // Steps 5.a-d.
     uint32_t codePoint;
     if (!ToCodePoint(cx, code, &codePoint))
         return false;
 
@@ -2850,18 +2850,18 @@ str_fromCodePoint_few_args(JSContext* cx
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 // ES2017 draft rev 40edb3a95a475c1b251141ac681b8793129d9a6d
 // 21.1.2.2 String.fromCodePoint(...codePoints)
-static bool
-str_fromCodePoint(JSContext* cx, unsigned argc, Value* vp)
+bool
+js::str_fromCodePoint(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Optimize the single code-point case.
     if (args.length() == 1)
         return str_fromCodePoint_one_arg(cx, args[0], args.rval());
 
     // Optimize the case where the result will definitely fit in an inline
@@ -2904,18 +2904,18 @@ str_fromCodePoint(JSContext* cx, unsigne
     }
 
     args.rval().setString(str);
     return true;
 }
 
 static const JSFunctionSpec string_static_methods[] = {
     JS_INLINABLE_FN("fromCharCode", js::str_fromCharCode, 1, 0, StringFromCharCode),
-
-    JS_FN("fromCodePoint",               str_fromCodePoint,             1,0),
+    JS_INLINABLE_FN("fromCodePoint", js::str_fromCodePoint, 1, 0, StringFromCodePoint),
+
     JS_SELF_HOSTED_FN("raw",             "String_static_raw",           2,JSFUN_HAS_REST),
     JS_SELF_HOSTED_FN("substring",       "String_static_substring",     3,0),
     JS_SELF_HOSTED_FN("substr",          "String_static_substr",        3,0),
     JS_SELF_HOSTED_FN("slice",           "String_static_slice",         3,0),
 
     JS_SELF_HOSTED_FN("match",           "String_generic_match",        2,0),
     JS_SELF_HOSTED_FN("replace",         "String_generic_replace",      3,0),
     JS_SELF_HOSTED_FN("search",          "String_generic_search",       2,0),
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -313,16 +313,22 @@ DeflateStringToBuffer(JSContext* maybecx
                       size_t charsLength, char* bytes, size_t* length);
 
 extern bool
 str_fromCharCode(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 str_fromCharCode_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval);
 
+extern bool
+str_fromCodePoint(JSContext* cx, unsigned argc, Value* vp);
+
+extern bool
+str_fromCodePoint_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval);
+
 /* String methods exposed so they can be installed in the self-hosting global. */
 
 extern bool
 str_includes(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 str_indexOf(JSContext* cx, unsigned argc, Value* vp);