From 8c2202027e9acf144b14f34662e4cad11be463b4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Andr=C3=A9=20Bargull?= Date: Mon, 22 Jan 2024 13:28:46 +0000 Subject: [PATCH] Bug 1874683 - Part 6: Inline String.prototype.codePointAt in CacheIR. r=jandem Differential Revision: https://phabricator.services.mozilla.com/D198562 --- js/src/builtin/String.cpp | 2 +- .../tests/cacheir/string-codePointAt-oob.js | 151 +++++++++++++++++++++ .../tests/cacheir/string-codePointAt-rope.js | 80 +++++++++++ .../tests/cacheir/string-codePointAt-surrogate.js | 103 ++++++++++++++ js/src/jit/CacheIR.cpp | 70 ++++++++-- js/src/jit/CacheIRCompiler.cpp | 95 +++++++++++++ js/src/jit/CacheIRGenerator.h | 3 +- js/src/jit/CacheIROps.yaml | 18 +++ js/src/jit/InlinableNatives.cpp | 1 + js/src/jit/InlinableNatives.h | 1 + 10 files changed, 507 insertions(+), 17 deletions(-) create mode 100644 js/src/jit-test/tests/cacheir/string-codePointAt-oob.js create mode 100644 js/src/jit-test/tests/cacheir/string-codePointAt-rope.js create mode 100644 js/src/jit-test/tests/cacheir/string-codePointAt-surrogate.js diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index b32aeb9e76fc..fe2c9a9570dc 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -3842,7 +3842,7 @@ static const JSFunctionSpec string_methods[] = { JS_INLINABLE_FN("toUpperCase", str_toUpperCase, 0, 0, StringToUpperCase), JS_INLINABLE_FN("charAt", str_charAt, 1, 0, StringCharAt), JS_INLINABLE_FN("charCodeAt", str_charCodeAt, 1, 0, StringCharCodeAt), - JS_FN("codePointAt", str_codePointAt, 1, 0), + JS_INLINABLE_FN("codePointAt", str_codePointAt, 1, 0, StringCodePointAt), JS_SELF_HOSTED_FN("substring", "String_substring", 2, 0), JS_SELF_HOSTED_FN("padStart", "String_pad_start", 2, 0), JS_SELF_HOSTED_FN("padEnd", "String_pad_end", 2, 0), diff --git a/js/src/jit-test/tests/cacheir/string-codePointAt-oob.js b/js/src/jit-test/tests/cacheir/string-codePointAt-oob.js new file mode 100644 index 000000000000..f456361b5ef4 --- /dev/null +++ b/js/src/jit-test/tests/cacheir/string-codePointAt-oob.js @@ -0,0 +1,151 @@ +// Test String.prototype.codePointAt with out-of-bounds indices. + +function* characters(...ranges) { + for (let [start, end] of ranges) { + for (let i = start; i <= end; ++i) { + yield i; + } + } +} + +const empty = []; + +const ascii = [...characters( + [0x41, 0x5A], // A..Z + [0x61, 0x7A], // a..z +)]; + +const latin1 = [...characters( + [0xC0, 0xFF], // À..ÿ +)]; + +const twoByte = [...characters( + [0x100, 0x17E], // Ā..ž +)]; + +function atomize(s) { + return Object.keys({[s]: 0})[0]; +} + +function codePoints() { + return [empty, ascii, latin1, twoByte]; +} + +function toRope(s) { + // Ropes have at least two characters. + if (s.length < 2) { + return s; + } + if (s.length === 2) { + return newRope(s[0], s[1]); + } + return newRope(s[0], s.substring(1)); +} + +function makeStrings() { + let strings = codePoints() + .map(codePoints => String.fromCodePoint(...codePoints)) + .flatMap(x => [ + x, + toRope(x), + newString(x, {twoByte: true}), + atomize(x), + ]); + return strings; +} + +function testNegativeIndexConstant() { + let strings = makeStrings(); + for (let i = 0; i < 200; ++i) { + let str = strings[i % strings.length]; + let ch = str.codePointAt(-1); + assertEq(ch, undefined); + } +} +for (let i = 0; i < 2; ++i) { + testNegativeIndexConstant(); +} + +function testNegativeIndexVariable() { + let indices = [-1, -2]; + let strings = makeStrings(); + for (let i = 0; i < 200; ++i) { + let str = strings[i % strings.length]; + let ch = str.codePointAt(indices[i & 1]); + assertEq(ch, undefined); + } +} +for (let i = 0; i < 2; ++i) { + testNegativeIndexVariable(); +} + +function testNegativeOrValidIndex() { + let indices = [-1, 0]; + let strings = makeStrings(); + + // Number of string kinds created in makeStrings. + const N = 4; + + let cpoints = codePoints(); + assertEq(strings.length, cpoints.length * N); + + for (let i = 0; i < 200; ++i) { + let str = strings[i % strings.length]; + let index = indices[i & 1]; + let ch = str.codePointAt(index); + + let cp = cpoints[Math.trunc((i % strings.length) / N)]; + assertEq(ch, (0 <= index && index < cp.length ? cp[index] : undefined)); + } +} +for (let i = 0; i < 2; ++i) { + testNegativeOrValidIndex(); +} + +function testTooLargeIndexConstant() { + let strings = makeStrings(); + for (let i = 0; i < 200; ++i) { + let str = strings[i % strings.length]; + let ch = str.codePointAt(1000); + assertEq(ch, undefined); + } +} +for (let i = 0; i < 2; ++i) { + testTooLargeIndexConstant(); +} + +function testTooLargeIndexVariable() { + let indices = [1000, 2000]; + let strings = makeStrings(); + for (let i = 0; i < 200; ++i) { + let str = strings[i % strings.length]; + let ch = str.codePointAt(indices[i & 1]); + assertEq(ch, undefined); + } +} +for (let i = 0; i < 2; ++i) { + testTooLargeIndexVariable(); +} + +function testTooLargeOrValidIndex() { + let indices = [1000, 0]; + let strings = makeStrings(); + + // Number of string kinds created in makeStrings. + const N = 4; + + let cpoints = codePoints(); + assertEq(strings.length, cpoints.length * N); + + for (let i = 0; i < 200; ++i) { + let str = strings[i % strings.length]; + let index = indices[i & 1]; + let ch = str.codePointAt(index); + + let cp = cpoints[Math.trunc((i % strings.length) / N)]; + assertEq(ch, (0 <= index && index < cp.length ? cp[index] : undefined)); + } +} +for (let i = 0; i < 2; ++i) { + testTooLargeOrValidIndex(); +} diff --git a/js/src/jit-test/tests/cacheir/string-codePointAt-rope.js b/js/src/jit-test/tests/cacheir/string-codePointAt-rope.js new file mode 100644 index 000000000000..76e16c7ea348 --- /dev/null +++ b/js/src/jit-test/tests/cacheir/string-codePointAt-rope.js @@ -0,0 +1,80 @@ +function makeRope() { + var left = newRope("@ABCDEFGHIJKLMNO", "PQRSTUVWXYZ[\\]^_"); + var right = newRope("`abcdefghijklmno", "pqrstuvwxyz{|}~"); + var rope = newRope(left, right); + return {left, right, rope}; +} + +// Load a character from the left rope child using a constant index. The input +// to String.prototype.codePointAt is always rope. +function testLeftChildConstant() { + for (var i = 0; i < 200; ++i) { + var {rope} = makeRope(); + + var ch = rope.codePointAt(0); + assertEq(ch, 0x40); + } +} +for (var i = 0; i < 2; ++i) { + testLeftChildConstant(); +} + +// Load a character from the right rope child using a constant index. The input +// to String.prototype.codePointAt is always rope. +function testRightChildConstant() { + for (var i = 0; i < 200; ++i) { + var {rope} = makeRope(); + + var ch = rope.codePointAt(32); + assertEq(ch, 0x60); + } +} +for (var i = 0; i < 2; ++i) { + testRightChildConstant(); +} + +// Load a character from the left rope child using a variable index. The input +// to String.prototype.codePointAt is always rope. +function testLeftChildVariable() { + for (var i = 0; i < 200; ++i) { + var {left, rope} = makeRope(); + + var idx = i % left.length; + var ch = rope.codePointAt(idx); + assertEq(ch, 0x40 + idx); + } +} +for (var i = 0; i < 2; ++i) { + testLeftChildVariable(); +} + +// Load a character from the right rope child using a variable index. The input +// to String.prototype.codePointAt is always rope. +function testRightChildVariable() { + for (var i = 0; i < 200; ++i) { + var {left, right, rope} = makeRope(); + + var idx = i % right.length; + var ch = rope.codePointAt(left.length + idx); + assertEq(ch, 0x60 + idx); + } +} +for (var i = 0; i < 2; ++i) { + testRightChildVariable(); +} + +// Load all characters from both child ropes. This covers the case when the +// call to String.prototype.codePointAt linearizes the rope. +function testBothChildren() { + for (var i = 0; i < 200; ++i) { + var {rope} = makeRope(); + + for (var j = 0; j < rope.length; ++j) { + var ch = rope.codePointAt(j); + assertEq(ch, 0x40 + j); + } + } +} +for (var i = 0; i < 2; ++i) { + testBothChildren(); +} diff --git a/js/src/jit-test/tests/cacheir/string-codePointAt-surrogate.js b/js/src/jit-test/tests/cacheir/string-codePointAt-surrogate.js new file mode 100644 index 000000000000..961953a5989b --- /dev/null +++ b/js/src/jit-test/tests/cacheir/string-codePointAt-surrogate.js @@ -0,0 +1,103 @@ +function testLinear() { + var s = "\u{10000}\u{10001}\u{10002}\u{10003}\u{10004}\u{10005}\u{10006}\u{10007}"; + + for (var i = 0; i < 200; ++i) { + // Iterate over all possible string indices, which includes reading + // unpaired lead surrogates. + var index = i % s.length; + + var c = s.codePointAt(index); + var e = ((index & 1) ? 0xdc00 : 0x10000) + (index >> 1); + assertEq(c, e); + } +} +for (var i = 0; i < 2; ++i) { + testLinear(); +} + +function testLinearOnlyValidCodePoints() { + var s = "\u{10000}\u{10001}\u{10002}\u{10003}\u{10004}\u{10005}\u{10006}\u{10007}"; + + for (var i = 0; i < 200; ++i) { + // Iterator over all valid code point indices. (Code points are at all even + // string indices.) + var index = (i % s.length) & ~1; + + var c = s.codePointAt(index); + var e = ((index & 1) ? 0xdc00 : 0x10000) + (index >> 1); + assertEq(c, e); + } +} +for (var i = 0; i < 2; ++i) { + testLinearOnlyValidCodePoints(); +} + +function testRope() { + var left = "\u{10000}\u{10001}\u{10002}\u{10003}\u{10004}\u{10005}\u{10006}\u{10007}"; + var right = "\u{10008}\u{10009}\u{1000A}\u{1000B}\u{1000C}\u{1000D}\u{1000E}\u{1000F}"; + + for (var i = 0; i < 200; ++i) { + var s = newRope(left, right); + + // Iterate over all possible string indices, which includes reading + // unpaired lead surrogates. + var index = i % s.length; + + var c = s.codePointAt(index); + var e = ((index & 1) ? 0xdc00 : 0x10000) + (index >> 1); + assertEq(c, e); + } +} +for (var i = 0; i < 2; ++i) { + testRope(); +} + +function testRopeOnlyValidCodePoints() { + var left = "\u{10000}\u{10001}\u{10002}\u{10003}\u{10004}\u{10005}\u{10006}\u{10007}"; + var right = "\u{10008}\u{10009}\u{1000A}\u{1000B}\u{1000C}\u{1000D}\u{1000E}\u{1000F}"; + + for (var i = 0; i < 200; ++i) { + var s = newRope(left, right); + + // Iterator over all valid code point indices. (Code points are at all even + // string indices.) + var index = (i % s.length) & ~1; + + var c = s.codePointAt(index); + var e = ((index & 1) ? 0xdc00 : 0x10000) + (index >> 1); + assertEq(c, e); + } +} +for (var i = 0; i < 2; ++i) { + testRopeOnlyValidCodePoints(); +} + +function testUnpairedLead() { + var s = "\u{d800}\u{d801}\u{d802}\u{d803}\u{d804}\u{d805}\u{d806}\u{d807}"; + + for (var i = 0; i < 200; ++i) { + var index = i % s.length; + + var c = s.codePointAt(index); + var e = 0xd800 + index; + assertEq(c, e); + } +} +for (var i = 0; i < 2; ++i) { + testUnpairedLead(); +} + +function testUnpairedTail() { + var s = "\u{dc00}\u{dc01}\u{dc02}\u{dc03}\u{dc04}\u{dc05}\u{dc06}\u{dc07}"; + + for (var i = 0; i < 200; ++i) { + var index = i % s.length; + + var c = s.codePointAt(index); + var e = 0xdc00 + index; + assertEq(c, e); + } +} +for (var i = 0; i < 2; ++i) { + testUnpairedTail(); +} diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index 4bf5ab71e1fe..1d819a00a441 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -2596,7 +2596,8 @@ AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId, enum class AttachStringChar { No, Yes, Linearize, OutOfBounds }; static AttachStringChar CanAttachStringChar(const Value& val, - const Value& idVal) { + const Value& idVal, + StringChar kind) { if (!val.isString() || !idVal.isInt32()) { return AttachStringChar::No; } @@ -2616,6 +2617,18 @@ static AttachStringChar CanAttachStringChar(const Value& val, JSRope* rope = &str->asRope(); if (size_t(index) < rope->leftChild()->length()) { str = rope->leftChild(); + + // MacroAssembler::loadStringChar doesn't support surrogate pairs which + // are split between the left and right child of a rope. + if (kind == StringChar::CodePointAt && + size_t(index) + 1 == str->length() && str->isLinear()) { + // Linearize the string when the last character of the left child is a + // a lead surrogate. + char16_t ch = str->asLinear().latin1OrTwoByteChar(index); + if (unicode::IsLeadSurrogate(ch)) { + return AttachStringChar::Linearize; + } + } } else { str = rope->rightChild(); } @@ -2632,7 +2645,7 @@ AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, ValOperandId indexId) { MOZ_ASSERT(idVal_.isInt32()); - auto attach = CanAttachStringChar(val_, idVal_); + auto attach = CanAttachStringChar(val_, idVal_, StringChar::CharAt); if (attach == AttachStringChar::No) { return AttachDecision::NoAction; } @@ -7313,7 +7326,7 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStringChar( return AttachDecision::NoAction; } - auto attach = CanAttachStringChar(thisval_, args_[0]); + auto attach = CanAttachStringChar(thisval_, args_[0], kind); if (attach == AttachStringChar::No) { return AttachDecision::NoAction; } @@ -7323,7 +7336,8 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStringChar( // Initialize the input operand. initializeInputOperand(); - // Guard callee is the 'charCodeAt' or 'charAt' native function. + // Guard callee is the 'charCodeAt', 'codePointAt', or 'charAt' native + // function. emitNativeCalleeGuard(); // Guard this is a string. @@ -7343,33 +7357,57 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStringChar( // for out-of-bounds accesses. if (attach == AttachStringChar::Linearize || attach == AttachStringChar::OutOfBounds) { - strId = writer.linearizeForCharAccess(strId, int32IndexId); + switch (kind) { + case StringChar::CharCodeAt: + case StringChar::CharAt: + strId = writer.linearizeForCharAccess(strId, int32IndexId); + break; + case StringChar::CodePointAt: + strId = writer.linearizeForCodePointAccess(strId, int32IndexId); + break; + } } // Load string char or code. - if (kind == StringChar::CodeAt) { - writer.loadStringCharCodeResult(strId, int32IndexId, handleOOB); - } else { - writer.loadStringCharResult(strId, int32IndexId, handleOOB); + switch (kind) { + case StringChar::CharCodeAt: + writer.loadStringCharCodeResult(strId, int32IndexId, handleOOB); + break; + case StringChar::CodePointAt: + writer.loadStringCodePointResult(strId, int32IndexId, handleOOB); + break; + case StringChar::CharAt: + writer.loadStringCharResult(strId, int32IndexId, handleOOB); + break; } writer.returnFromIC(); - if (kind == StringChar::CodeAt) { - trackAttached("StringCharCodeAt"); - } else { - trackAttached("StringCharAt"); + switch (kind) { + case StringChar::CharCodeAt: + trackAttached("StringCharCodeAt"); + break; + case StringChar::CodePointAt: + trackAttached("StringCodePointAt"); + break; + case StringChar::CharAt: + trackAttached("StringCharAt"); + break; } return AttachDecision::Attach; } AttachDecision InlinableNativeIRGenerator::tryAttachStringCharCodeAt() { - return tryAttachStringChar(StringChar::CodeAt); + return tryAttachStringChar(StringChar::CharCodeAt); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringCodePointAt() { + return tryAttachStringChar(StringChar::CodePointAt); } AttachDecision InlinableNativeIRGenerator::tryAttachStringCharAt() { - return tryAttachStringChar(StringChar::At); + return tryAttachStringChar(StringChar::CharAt); } AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCharCode() { @@ -11154,6 +11192,8 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() { return tryAttachStringToStringValueOf(); case InlinableNative::StringCharCodeAt: return tryAttachStringCharCodeAt(); + case InlinableNative::StringCodePointAt: + return tryAttachStringCodePointAt(); case InlinableNative::StringCharAt: return tryAttachStringCharAt(); case InlinableNative::StringFromCharCode: diff --git a/js/src/jit/CacheIRCompiler.cpp b/js/src/jit/CacheIRCompiler.cpp index f52095e2a818..29a21af12153 100644 --- a/js/src/jit/CacheIRCompiler.cpp +++ b/js/src/jit/CacheIRCompiler.cpp @@ -3874,6 +3874,7 @@ bool CacheIRCompiler::emitLoadFunctionNameResult(ObjOperandId objId) { bool CacheIRCompiler::emitLinearizeForCharAccess(StringOperandId strId, Int32OperandId indexId, StringOperandId resultId) { + JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); Register str = allocator.useRegister(masm, strId); Register index = allocator.useRegister(masm, indexId); Register result = allocator.defineRegister(masm, resultId); @@ -3914,6 +3915,50 @@ bool CacheIRCompiler::emitLinearizeForCharAccess(StringOperandId strId, return true; } +bool CacheIRCompiler::emitLinearizeForCodePointAccess( + StringOperandId strId, Int32OperandId indexId, StringOperandId resultId) { + JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); + Register str = allocator.useRegister(masm, strId); + Register index = allocator.useRegister(masm, indexId); + Register result = allocator.defineRegister(masm, resultId); + AutoScratchRegister scratch1(allocator, masm); + AutoScratchRegister scratch2(allocator, masm); + + FailurePath* failure; + if (!addFailurePath(&failure)) { + return false; + } + + Label done; + masm.movePtr(str, result); + + // We can omit the bounds check, because we only compare the index against the + // string length. In the worst case we unnecessarily linearize the string + // when the index is out-of-bounds. + + masm.branchIfCanLoadStringCodePoint(str, index, scratch1, scratch2, &done); + { + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), + liveVolatileFloatRegs()); + masm.PushRegsInMask(volatileRegs); + + using Fn = JSLinearString* (*)(JSString*); + masm.setupUnalignedABICall(scratch1); + masm.passABIArg(str); + masm.callWithABI(); + masm.storeCallPointerResult(result); + + LiveRegisterSet ignore; + ignore.add(result); + masm.PopRegsInMaskIgnore(volatileRegs, ignore); + + masm.branchTestPtr(Assembler::Zero, result, result, failure->label()); + } + + masm.bind(&done); + return true; +} + bool CacheIRCompiler::emitLoadStringLengthResult(StringOperandId strId) { JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); AutoOutputRegister output(*this); @@ -3974,6 +4019,56 @@ bool CacheIRCompiler::emitLoadStringCharCodeResult(StringOperandId strId, return true; } +bool CacheIRCompiler::emitLoadStringCodePointResult(StringOperandId strId, + Int32OperandId indexId, + bool handleOOB) { + JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); + AutoOutputRegister output(*this); + Register str = allocator.useRegister(masm, strId); + Register index = allocator.useRegister(masm, indexId); + AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output); + AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output); + AutoScratchRegister scratch3(allocator, masm); + + // Bounds check, load string char. + Label done; + if (!handleOOB) { + FailurePath* failure; + if (!addFailurePath(&failure)) { + return false; + } + + masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()), + scratch1, failure->label()); + masm.loadStringCodePoint(str, index, scratch1, scratch2, scratch3, + failure->label()); + } else { + // Return undefined for out-of-bounds access. + masm.moveValue(JS::UndefinedValue(), output.valueReg()); + + // The bounds check mustn't use a scratch register which aliases the output. + MOZ_ASSERT(!output.valueReg().aliases(scratch3)); + + // This CacheIR op is always preceded by |LinearizeForCodePointAccess|, so + // we're guaranteed to see no nested ropes or split surrogates. + Label loadFailed; + masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()), + scratch3, &done); + masm.loadStringCodePoint(str, index, scratch1, scratch2, scratch3, + &loadFailed); + + Label loadedChar; + masm.jump(&loadedChar); + masm.bind(&loadFailed); + masm.assumeUnreachable("loadStringCodePoint can't fail for linear strings"); + masm.bind(&loadedChar); + } + + masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg()); + masm.bind(&done); + return true; +} + bool CacheIRCompiler::emitNewStringObjectResult(uint32_t templateObjectOffset, StringOperandId strId) { JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); diff --git a/js/src/jit/CacheIRGenerator.h b/js/src/jit/CacheIRGenerator.h index 7a0c223e03dd..fb51995827d8 100644 --- a/js/src/jit/CacheIRGenerator.h +++ b/js/src/jit/CacheIRGenerator.h @@ -504,7 +504,7 @@ class MOZ_RAII OptimizeGetIteratorIRGenerator : public IRGenerator { void trackAttached(const char* name /* must be a C string literal */); }; -enum class StringChar { CodeAt, At }; +enum class StringChar { CharCodeAt, CodePointAt, CharAt }; enum class ScriptedThisResult { NoAction, UninitializedThis, PlainObjectShape }; class MOZ_RAII CallIRGenerator : public IRGenerator { @@ -648,6 +648,7 @@ class MOZ_RAII InlinableNativeIRGenerator { AttachDecision tryAttachStringToStringValueOf(); AttachDecision tryAttachStringChar(StringChar kind); AttachDecision tryAttachStringCharCodeAt(); + AttachDecision tryAttachStringCodePointAt(); AttachDecision tryAttachStringCharAt(); AttachDecision tryAttachStringFromCharCode(); AttachDecision tryAttachStringFromCodePoint(); diff --git a/js/src/jit/CacheIROps.yaml b/js/src/jit/CacheIROps.yaml index a7e99cd9c056..e0e39bd0e86c 100644 --- a/js/src/jit/CacheIROps.yaml +++ b/js/src/jit/CacheIROps.yaml @@ -2181,6 +2181,15 @@ index: Int32Id result: StringId +- name: LinearizeForCodePointAccess + shared: true + transpile: false + cost_estimate: 4 + args: + str: StringId + index: Int32Id + result: StringId + - name: LoadStringCharResult shared: false transpile: true @@ -2199,6 +2208,15 @@ index: Int32Id handleOOB: BoolImm +- name: LoadStringCodePointResult + shared: true + transpile: false + cost_estimate: 3 + args: + str: StringId + index: Int32Id + handleOOB: BoolImm + - name: LoadStringLengthResult shared: true transpile: true diff --git a/js/src/jit/InlinableNatives.cpp b/js/src/jit/InlinableNatives.cpp index de41aab530f3..56c0731e6ebd 100644 --- a/js/src/jit/InlinableNatives.cpp +++ b/js/src/jit/InlinableNatives.cpp @@ -289,6 +289,7 @@ bool js::jit::CanInlineNativeCrossRealm(InlinableNative native) { case InlinableNative::StringToString: case InlinableNative::StringValueOf: case InlinableNative::StringCharCodeAt: + case InlinableNative::StringCodePointAt: case InlinableNative::StringFromCharCode: case InlinableNative::StringFromCodePoint: case InlinableNative::StringCharAt: diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h index b5cc579c3246..f30af2699546 100644 --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -136,6 +136,7 @@ _(StringToString) \ _(StringValueOf) \ _(StringCharCodeAt) \ + _(StringCodePointAt) \ _(StringFromCharCode) \ _(StringFromCodePoint) \ _(StringCharAt) \ -- 2.11.4.GIT