tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit fd6d791295d9458338f9d91c639a20fbda84a62a
parent c03efb6f2ec6d68c6de2fd43394b3d65141381d0
Author: André Bargull <andre.bargull@gmail.com>
Date:   Mon, 13 Oct 2025 12:55:26 +0000

Bug 1990248 - Part 7: Add inlining support for locale-sensitive case conversion. r=spidermonkey-reviewers,jandem

Most locales use the default case mapping algorithm, so we can reuse the
existing inlining support.

Differential Revision: https://phabricator.services.mozilla.com/D265829

Diffstat:
Mjs/src/builtin/String.cpp | 6++++--
Ajs/src/jit-test/tests/cacheir/string-toLocaleLowerCase-default-case-mapping-2.js | 14++++++++++++++
Ajs/src/jit-test/tests/cacheir/string-toLocaleLowerCase-default-case-mapping.js | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/jit-test/tests/cacheir/string-toLocaleUpperCase-default-case-mapping-2.js | 14++++++++++++++
Ajs/src/jit-test/tests/cacheir/string-toLocaleUpperCase-default-case-mapping.js | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/CacheIR.cpp | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/CacheIRGenerator.h | 2++
Mjs/src/jit/InlinableNatives.cpp | 2++
Mjs/src/jit/InlinableNatives.h | 2++
Mjs/src/vm/Realm.cpp | 8++++++++
Mjs/src/vm/Realm.h | 4+---
11 files changed, 267 insertions(+), 5 deletions(-)

diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp @@ -3899,8 +3899,10 @@ static const JSFunctionSpec string_methods[] = { JS_INLINABLE_FN("trim", str_trim, 0, 0, StringTrim), JS_INLINABLE_FN("trimStart", str_trimStart, 0, 0, StringTrimStart), JS_INLINABLE_FN("trimEnd", str_trimEnd, 0, 0, StringTrimEnd), - JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0, 0), - JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0, 0), + JS_INLINABLE_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0, 0, + StringToLocaleLowerCase), + JS_INLINABLE_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0, 0, + StringToLocaleUpperCase), JS_FN("localeCompare", str_localeCompare, 1, 0), JS_SELF_HOSTED_FN("repeat", "String_repeat", 1, 0), #if JS_HAS_INTL_API diff --git a/js/src/jit-test/tests/cacheir/string-toLocaleLowerCase-default-case-mapping-2.js b/js/src/jit-test/tests/cacheir/string-toLocaleLowerCase-default-case-mapping-2.js @@ -0,0 +1,14 @@ +// |jit-test| skip-if: typeof Intl === 'undefined' + +function test() { + for (var i = 0; i <= 100; ++i) { + if (i === 100) { + setRealmLocale("tr-TR"); + } + assertEq( + "TURKISH I".toLocaleLowerCase(), + i < 100 ? "turkish i" : "turk\u{131}sh \u{131}", + ); + } +} +test(); diff --git a/js/src/jit-test/tests/cacheir/string-toLocaleLowerCase-default-case-mapping.js b/js/src/jit-test/tests/cacheir/string-toLocaleLowerCase-default-case-mapping.js @@ -0,0 +1,58 @@ +// |jit-test| skip-if: typeof Intl === 'undefined' + +function testDefaultCaseMapping() { + for (var i = 0; i < 100; ++i) { + assertEq("TURKISH I".toLocaleLowerCase(), "turkish i"); + } +} + +function testTurkishCaseMapping() { + for (var i = 0; i < 100; ++i) { + assertEq("TURKISH I".toLocaleLowerCase(), "turk\u{131}sh \u{131}"); + } +} + +// JIT tests run with "en-US" by default. (Or "en-US-POSIX" for some Android tests.) +assertEq( + getDefaultLocale() === "en-US" || getDefaultLocale() === "en-US-POSIX", + true +); +assertEq(getRealmLocale(), "en-US"); + +// Ensure case mapping fuse is intact. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, true); + +// Run default case mapping test. +testDefaultCaseMapping(); + +// Change runtime default locale. (Use three letter language code to cover canonicalization.) +setDefaultLocale("fra-FR"); +assertEq(getDefaultLocale(), "fra-FR"); +assertEq(getRealmLocale(), "fr-FR"); + +// Ensure case mapping fuse is still intact. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, true); + +// Run default case mapping test again. +testDefaultCaseMapping(); + +// Change runtime default locale. +setDefaultLocale("tur-TR"); +assertEq(getDefaultLocale(), "tur-TR"); +assertEq(getRealmLocale(), "tr-TR"); + +// Case mapping fuse is no longer intact. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, false); + +testTurkishCaseMapping(); + +// Reset default locale. +setDefaultLocale("eng-US"); +assertEq(getDefaultLocale(), "eng-US"); +assertEq(getRealmLocale(), "en-US"); + +// Fuse is still popped. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, false); + +// Run default case mapping test. +testDefaultCaseMapping(); diff --git a/js/src/jit-test/tests/cacheir/string-toLocaleUpperCase-default-case-mapping-2.js b/js/src/jit-test/tests/cacheir/string-toLocaleUpperCase-default-case-mapping-2.js @@ -0,0 +1,14 @@ +// |jit-test| skip-if: typeof Intl === 'undefined' + +function test() { + for (var i = 0; i <= 100; ++i) { + if (i === 100) { + setRealmLocale("tr-TR"); + } + assertEq( + "turkish i".toLocaleUpperCase(), + i < 100 ? "TURKISH I" : "TURK\u{130}SH \u{130}", + ); + } +} +test(); diff --git a/js/src/jit-test/tests/cacheir/string-toLocaleUpperCase-default-case-mapping.js b/js/src/jit-test/tests/cacheir/string-toLocaleUpperCase-default-case-mapping.js @@ -0,0 +1,58 @@ +// |jit-test| skip-if: typeof Intl === 'undefined' + +function testDefaultCaseMapping() { + for (var i = 0; i < 100; ++i) { + assertEq("turkish i".toLocaleUpperCase(), "TURKISH I"); + } +} + +function testTurkishCaseMapping() { + for (var i = 0; i < 100; ++i) { + assertEq("turkish i".toLocaleUpperCase(), "TURK\u{130}SH \u{130}"); + } +} + +// JIT tests run with "en-US" by default. (Or "en-US-POSIX" for some Android tests.) +assertEq( + getDefaultLocale() === "en-US" || getDefaultLocale() === "en-US-POSIX", + true +); +assertEq(getRealmLocale(), "en-US"); + +// Ensure case mapping fuse is intact. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, true); + +// Run default case mapping test. +testDefaultCaseMapping(); + +// Change runtime default locale. +setDefaultLocale("fr-FR"); +assertEq(getDefaultLocale(), "fr-FR"); +assertEq(getRealmLocale(), "fr-FR"); + +// Ensure case mapping fuse is still intact. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, true); + +// Run default case mapping test again. +testDefaultCaseMapping(); + +// Change runtime default locale. +setDefaultLocale("tr-TR"); +assertEq(getDefaultLocale(), "tr-TR"); +assertEq(getRealmLocale(), "tr-TR"); + +// Case mapping fuse is no longer intact. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, false); + +testTurkishCaseMapping(); + +// Reset default locale. +setDefaultLocale("en-US"); +assertEq(getDefaultLocale(), "en-US"); +assertEq(getRealmLocale(), "en-US"); + +// Fuse is still popped. +assertEq(getFuseState().DefaultLocaleHasDefaultCaseMappingFuse.intact, false); + +// Run default case mapping test. +testDefaultCaseMapping(); diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp @@ -8823,6 +8823,106 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStringToUpperCase() { return AttachDecision::Attach; } +AttachDecision InlinableNativeIRGenerator::tryAttachStringToLocaleLowerCase() { +#if JS_HAS_INTL_API + // Expecting no arguments. + if (args_.length() != 0) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive string value. + if (!thisval_.isString()) { + return AttachDecision::NoAction; + } + + // Don't inline when not using the default locale. + if (cx_->realm()->behaviors().localeOverride()) { + return AttachDecision::NoAction; + } + + // Default case mapping fuse must still be intact. + if (!cx_->runtime() + ->runtimeFuses.ref() + .defaultLocaleHasDefaultCaseMappingFuse.intact()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + Int32OperandId argcId = initializeInputOperand(); + + // Guard callee is the 'toLocaleLowerCase' native function. + ObjOperandId calleeId = emitNativeCalleeGuard(argcId); + + // Guard this is a string. + ValOperandId thisValId = loadThis(calleeId); + StringOperandId strId = writer.guardToString(thisValId); + + // Guard runtime default locale uses default case mapping. + writer.guardRuntimeFuse( + RuntimeFuses::FuseIndex::DefaultLocaleHasDefaultCaseMappingFuse); + + // Return string converted to lower-case. + writer.stringToLowerCaseResult(strId); + writer.returnFromIC(); + + trackAttached("StringToLocaleLowerCase"); + return AttachDecision::Attach; +#else + // No inlining when Intl support is disabled. + return AttachDecision::Attach; +#endif +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringToLocaleUpperCase() { +#if JS_HAS_INTL_API + // Expecting no arguments. + if (args_.length() != 0) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive string value. + if (!thisval_.isString()) { + return AttachDecision::NoAction; + } + + // Don't inline when not using the default locale. + if (cx_->realm()->behaviors().localeOverride()) { + return AttachDecision::NoAction; + } + + // Default case mapping fuse must still be intact. + if (!cx_->runtime() + ->runtimeFuses.ref() + .defaultLocaleHasDefaultCaseMappingFuse.intact()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + Int32OperandId argcId = initializeInputOperand(); + + // Guard callee is the 'toLocaleUpperCase' native function. + ObjOperandId calleeId = emitNativeCalleeGuard(argcId); + + // Guard this is a string. + ValOperandId thisValId = loadThis(calleeId); + StringOperandId strId = writer.guardToString(thisValId); + + // Guard runtime default locale uses default case mapping. + writer.guardRuntimeFuse( + RuntimeFuses::FuseIndex::DefaultLocaleHasDefaultCaseMappingFuse); + + // Return string converted to upper-case. + writer.stringToUpperCaseResult(strId); + writer.returnFromIC(); + + trackAttached("StringToLocaleUpperCase"); + return AttachDecision::Attach; +#else + // No inlining when Intl support is disabled. + return AttachDecision::Attach; +#endif +} + AttachDecision InlinableNativeIRGenerator::tryAttachStringTrim() { // Expecting no arguments. if (args_.length() != 0) { @@ -13126,6 +13226,10 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() { return tryAttachStringToLowerCase(); case InlinableNative::StringToUpperCase: return tryAttachStringToUpperCase(); + case InlinableNative::StringToLocaleLowerCase: + return tryAttachStringToLocaleLowerCase(); + case InlinableNative::StringToLocaleUpperCase: + return tryAttachStringToLocaleUpperCase(); case InlinableNative::StringTrim: return tryAttachStringTrim(); case InlinableNative::StringTrimStart: diff --git a/js/src/jit/CacheIRGenerator.h b/js/src/jit/CacheIRGenerator.h @@ -786,6 +786,8 @@ class MOZ_RAII InlinableNativeIRGenerator { AttachDecision tryAttachStringEndsWith(); AttachDecision tryAttachStringToLowerCase(); AttachDecision tryAttachStringToUpperCase(); + AttachDecision tryAttachStringToLocaleLowerCase(); + AttachDecision tryAttachStringToLocaleUpperCase(); AttachDecision tryAttachStringTrim(); AttachDecision tryAttachStringTrimStart(); AttachDecision tryAttachStringTrimEnd(); diff --git a/js/src/jit/InlinableNatives.cpp b/js/src/jit/InlinableNatives.cpp @@ -342,6 +342,8 @@ bool js::jit::CanInlineNativeCrossRealm(InlinableNative native) { case InlinableNative::StringEndsWith: case InlinableNative::StringToLowerCase: case InlinableNative::StringToUpperCase: + case InlinableNative::StringToLocaleLowerCase: + case InlinableNative::StringToLocaleUpperCase: case InlinableNative::StringTrim: case InlinableNative::StringTrimStart: case InlinableNative::StringTrimEnd: diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h @@ -184,6 +184,8 @@ _(StringEndsWith) \ _(StringToLowerCase) \ _(StringToUpperCase) \ + _(StringToLocaleLowerCase) \ + _(StringToLocaleUpperCase) \ _(StringTrim) \ _(StringTrimStart) \ _(StringTrimEnd) \ diff --git a/js/src/vm/Realm.cpp b/js/src/vm/Realm.cpp @@ -536,6 +536,14 @@ const char* Realm::getLocale() const { return runtime_->getDefaultLocale(); } +void Realm::setLocaleOverride(const char* locale) { + // Clear any jitcode in the runtime, because compiled code doesn't handle + // updates to a realm's locale override. + ReleaseAllJITCode(runtime_->gcContext()); + + behaviors_.setLocaleOverride(locale); +} + js::DateTimeInfo* Realm::getDateTimeInfo() { #if JS_HAS_INTL_API if (RefPtr<TimeZoneString> timeZone = behaviors_.timeZoneOverride()) { diff --git a/js/src/vm/Realm.h b/js/src/vm/Realm.h @@ -807,9 +807,7 @@ class JS::Realm : public JS::shadow::Realm { // Set the locale for this realm. Reset to the system default locale when the // input is |nullptr|. - void setLocaleOverride(const char* locale) { - behaviors_.setLocaleOverride(locale); - } + void setLocaleOverride(const char* locale); // Returns the date-time info for this realm. Returns nullptr unless a time // zone override was specified in the realm creation options.