Skip to content

Commit a1a3d50

Browse files
committed
[MERGE #5651 @jackhorton] Project C++ enums to Intl.js, implement Intl.Collator's "usage" option
Merge pull request #5651 from jackhorton:intl/projected-enums-and-usage Also, some assorted fixes and cleanup: - We now correctly set the initSlotCapacity of the Intl native interface object - Moved around some of the asserts in GetLocaleData to be more defensive. None hit, just noticed they had gaps. - Added caching to Number.prototype.toLocaleString, which is now ~O(1) when calling with default arguments (locally on a debug build, time spent calling toLocaleString 100 times went from 140ms to 20ms) Fixes #5648 Fixes #4007
2 parents e2ca437 + 962624d commit a1a3d50

13 files changed

+29549
-30451
lines changed

lib/Runtime/Base/JnDirectFields.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,10 +519,36 @@ ENTRY(tagPublicLibraryCode)
519519
ENTRY(winglob)
520520
ENTRY(platform)
521521
ENTRY(formatToParts)
522-
ENTRY(FallbackSymbol)
522+
ENTRY(co)
523+
ENTRY(kn)
524+
ENTRY(kf)
525+
ENTRY(nu)
526+
ENTRY(ca)
527+
ENTRY(hc)
528+
ENTRY(upper)
529+
ENTRY(lower)
530+
ENTRY(base)
531+
ENTRY(accent)
532+
ENTRY2(case_, _u("case"))
533+
ENTRY(variant)
534+
ENTRY(code)
535+
ENTRY(decimal)
536+
ENTRY(percent)
537+
ENTRY(NumberFormatStyle)
538+
ENTRY(NumberFormatCurrencyDisplay)
539+
ENTRY(CollatorSensitivity)
540+
ENTRY(CollatorCaseFirst)
541+
ENTRY(LocaleDataKind)
542+
ENTRY(DateToLocaleString)
543+
ENTRY(DateToLocaleDateString)
544+
ENTRY(DateToLocaleTimeString)
545+
ENTRY(NumberToLocaleString)
546+
ENTRY(StringLocaleCompare)
547+
ENTRY(BuiltInFunctionID)
523548

524549
// This symbol is not part of the regular Symbol API and is only used in rare circumstances in Intl.js for backwards compatibility
525550
// with the Intl v1 spec. It is visible to the user only using Object.getOwnPropertySymbols(Intl.NumberFormat.call(new Intl.NumberFormat())).
551+
ENTRY(FallbackSymbol)
526552
ENTRY_SYMBOL(_intlFallbackSymbol, _u("Intl.FallbackSymbol"))
527553

528554
ENTRY(NumberFormat)
@@ -561,6 +587,7 @@ ENTRY(numeric)
561587
ENTRY(sensitivity)
562588
ENTRY(sensitivityEnum)
563589
ENTRY(caseFirstEnum)
590+
ENTRY(usage)
564591

565592
ENTRY(DateTimeFormat)
566593
ENTRY(__boundFormat)

lib/Runtime/ByteCode/ByteCodeCacheReleaseFileVersion.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
44
//-------------------------------------------------------------------------------------------------------
55
// NOTE: If there is a merge conflict the correct fix is to make a new GUID.
6-
// This file was generated with tools\update_bytecode_version.ps1
76

8-
// {BAC3A947-4873-4DAF-AC60-915116FFE744}
7+
// {DE720CA5-2D6E-4200-B4AB-CCCE40399727}
98
const GUID byteCodeCacheReleaseFileVersion =
10-
{ 0xBAC3A947, 0x4873, 0x4DAF, { 0xAC, 0x60, 0x91, 0x51, 0x16, 0xFF, 0xE7, 0x44 } };
9+
{ 0xDE720CA5, 0x2D6E, 0x4200, { 0xB4, 0xAB, 0xCC, 0xCE, 0x40, 0x39, 0x97, 0x27 } };

lib/Runtime/Library/InJavascript/Intl.js

Lines changed: 35 additions & 111 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/InJavascript/Intl.js.bc.32b.h

Lines changed: 7553 additions & 7786 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/InJavascript/Intl.js.bc.64b.h

Lines changed: 7552 additions & 7785 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.32b.h

Lines changed: 6889 additions & 7115 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.64b.h

Lines changed: 6888 additions & 7114 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/IntlEngineInterfaceExtensionObject.cpp

Lines changed: 120 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -48,48 +48,76 @@ using namespace PlatformAgnostic::ICUHelpers;
4848

4949
#endif // INTL_ICU
5050

51-
// NOTE(jahorto): Keep these enums in sync with those by the same name in Intl.js
52-
// These enums are used by both WinGlob- and ICU-backed Intl
53-
enum class NumberFormatStyle
54-
{
55-
Decimal, // Intl.NumberFormat(locale, { style: "decimal" }); // aka in our code as "number"
56-
Percent, // Intl.NumberFormat(locale, { style: "percent" });
57-
Currency, // Intl.NumberFormat(locale, { style: "currency", ... });
58-
59-
Max,
60-
Default = Decimal,
61-
};
62-
63-
enum class NumberFormatCurrencyDisplay
64-
{
65-
Symbol, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "symbol" }); // e.g. "$" or "US$" depeding on locale
66-
Code, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "code" }); // e.g. "USD"
67-
Name, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "name" }); // e.g. "US dollar"
68-
69-
Max,
70-
Default = Symbol,
51+
// The following macros allow the key-value pairs to be C++ enums as well as JS objects
52+
// in Intl.js. When adding a new macro, follow the same format as the _VALUES macros below,
53+
// and add your new _VALUES macro to PROJECTED_ENUMS along with the name of the enum.
54+
// NOTE: make sure the last VALUE macro has the highest integer value, since the C++ enum's ::Max
55+
// value is added to the end of the C++ enum definition as an increment of the previous value.
56+
// The ::Max value is used in a defensive assert, and we want to make sure its always 1 greater
57+
// than the highest valid value.
58+
59+
#define NUMBERFORMATSTYLE_VALUES(VALUE) \
60+
VALUE(Default, default_, 0) \
61+
VALUE(Decimal, decimal, 0) \
62+
VALUE(Percent, percent, 1) \
63+
VALUE(Currency, currency, 2)
64+
65+
#define NUMBERFORMATCURRENCYDISPLAY_VALUES(VALUE) \
66+
VALUE(Default, default_, 0) \
67+
VALUE(Symbol, symbol, 0) \
68+
VALUE(Code, code, 1) \
69+
VALUE(Name, name, 2)
70+
71+
#define COLLATORSENSITIVITY_VALUES(VALUE) \
72+
VALUE(Default, default_, 3) \
73+
VALUE(Base, base, 0) \
74+
VALUE(Accent, accent, 1) \
75+
VALUE(Case, case_, 2) \
76+
VALUE(Variant, variant, 3)
77+
78+
#define COLLATORCASEFIRST_VALUES(VALUE) \
79+
VALUE(Default, default_, 2) \
80+
VALUE(Upper, upper, 0) \
81+
VALUE(Lower, lower, 1) \
82+
VALUE(False, false_, 2)
83+
84+
// LocaleDataKind intentionally has no Default value
85+
#define LOCALEDATAKIND_VALUES(VALUE) \
86+
VALUE(Collation, co, 0) \
87+
VALUE(CaseFirst, kf, 1) \
88+
VALUE(Numeric, kn, 2) \
89+
VALUE(Calendar, ca, 3) \
90+
VALUE(NumberingSystem, nu, 4) \
91+
VALUE(HourCycle, hc, 5)
92+
93+
//BuiltInFunctionID intentionally has no Default value
94+
#define BUILTINFUNCTIONID_VALUES(VALUE) \
95+
VALUE(DateToLocaleString, DateToLocaleString, 0) \
96+
VALUE(DateToLocaleDateString, DateToLocaleDateString, 1) \
97+
VALUE(DateToLocaleTimeString, DateToLocaleTimeString, 2) \
98+
VALUE(NumberToLocaleString, NumberToLocaleString, 3) \
99+
VALUE(StringLocaleCompare, StringLocaleCompare, 4)
100+
101+
#define ENUM_VALUE(enumName, propId, value) enumName = value,
102+
#define PROJECTED_ENUM(ClassName, VALUES) \
103+
enum class ClassName \
104+
{ \
105+
VALUES(ENUM_VALUE) \
106+
Max \
71107
};
72108

73-
enum class CollatorSensitivity
74-
{
75-
Base,
76-
Accent,
77-
Case,
78-
Variant,
109+
#define PROJECTED_ENUMS(PROJECT) \
110+
PROJECT(LocaleDataKind, LOCALEDATAKIND_VALUES) \
111+
PROJECT(CollatorCaseFirst, COLLATORCASEFIRST_VALUES) \
112+
PROJECT(CollatorSensitivity, COLLATORSENSITIVITY_VALUES) \
113+
PROJECT(NumberFormatCurrencyDisplay, NUMBERFORMATCURRENCYDISPLAY_VALUES) \
114+
PROJECT(NumberFormatStyle, NUMBERFORMATSTYLE_VALUES) \
115+
PROJECT(BuiltInFunctionID, BUILTINFUNCTIONID_VALUES)
79116

80-
Max,
81-
Default = Variant,
82-
};
117+
PROJECTED_ENUMS(PROJECTED_ENUM)
83118

84-
enum class CollatorCaseFirst
85-
{
86-
Upper,
87-
Lower,
88-
False,
89-
90-
Max,
91-
Default = False,
92-
};
119+
#undef PROJECTED_ENUM
120+
#undef ENUM_VALUE
93121

94122
#pragma warning(push)
95123
#pragma warning(disable:4309) // truncation of constant value
@@ -431,7 +459,7 @@ namespace Js
431459
template <size_t N>
432460
static void LangtagToLocaleID(_In_ JavascriptString *langtag, _Out_ char(&localeID)[N])
433461
{
434-
LangtagToLocaleID(langtag->GetSz(), langtag->GetLength(), localeID);
462+
LangtagToLocaleID(langtag->GetString(), langtag->GetLength(), localeID);
435463
}
436464

437465
template <typename Callback>
@@ -533,22 +561,46 @@ namespace Js
533561

534562
bool IntlEngineInterfaceExtensionObject::InitializeIntlNativeInterfaces(DynamicObject* intlNativeInterfaces, DeferredTypeHandlerBase * typeHandler, DeferredInitializeMode mode)
535563
{
536-
typeHandler->Convert(intlNativeInterfaces, mode, 16);
564+
int initSlotCapacity = 0;
565+
566+
// automatically get the initSlotCapacity from everything we are about to add to intlNativeInterfaces
567+
#define INTL_ENTRY(id, func) initSlotCapacity++;
568+
#include "IntlExtensionObjectBuiltIns.h"
569+
#undef INTL_ENTRY
570+
571+
#define PROJECTED_ENUM(ClassName, VALUES) initSlotCapacity++;
572+
PROJECTED_ENUMS(PROJECTED_ENUM)
573+
#undef PROJECTED_ENUM
574+
575+
// add capacity for platform.winglob and platform.FallbackSymbol
576+
initSlotCapacity += 2;
577+
578+
typeHandler->Convert(intlNativeInterfaces, mode, initSlotCapacity);
537579

538580
ScriptContext* scriptContext = intlNativeInterfaces->GetScriptContext();
539581
JavascriptLibrary* library = scriptContext->GetLibrary();
540582

541583
// gives each entrypoint a property ID on the intlNativeInterfaces library object
542-
#ifdef INTL_ENTRY
543-
#undef INTL_ENTRY
544-
#endif
545-
#define INTL_ENTRY(id, func) \
546-
library->AddFunctionToLibraryObject(intlNativeInterfaces, Js::PropertyIds::##id, &IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func, 1);
584+
#define INTL_ENTRY(id, func) library->AddFunctionToLibraryObject(intlNativeInterfaces, Js::PropertyIds::##id, &IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func, 1);
547585
#include "IntlExtensionObjectBuiltIns.h"
548586
#undef INTL_ENTRY
549587

550588
library->AddMember(intlNativeInterfaces, PropertyIds::FallbackSymbol, library->CreateSymbol(BuiltInPropertyRecords::_intlFallbackSymbol));
551589

590+
DynamicObject * enumObj = nullptr;
591+
592+
// Projects the exact layout of our C++ enums into Intl.js so that we dont have to remember to keep them in sync
593+
#define ENUM_VALUE(enumName, propId, value) library->AddMember(enumObj, PropertyIds::##propId, JavascriptNumber::ToVar(value, scriptContext));
594+
#define PROJECTED_ENUM(ClassName, VALUES) \
595+
enumObj = library->CreateObject(); \
596+
VALUES(ENUM_VALUE) \
597+
library->AddMember(intlNativeInterfaces, PropertyIds::##ClassName, enumObj); \
598+
599+
PROJECTED_ENUMS(PROJECTED_ENUM)
600+
601+
#undef PROJECTED_ENUM
602+
#undef ENUM_VALUE
603+
552604
#if INTL_WINGLOB
553605
library->AddMember(intlNativeInterfaces, Js::PropertyIds::winglob, library->GetTrue());
554606
#else
@@ -1049,15 +1101,8 @@ DEFINE_ISXLOCALEAVAILABLE(DTF, udat)
10491101
DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
10501102

10511103
#ifdef INTL_ICU
1052-
enum class LocaleDataKind
1053-
{
1054-
Collation,
1055-
CaseFirst,
1056-
Numeric,
1057-
Calendar,
1058-
NumberingSystem,
1059-
HourCycle
1060-
};
1104+
1105+
10611106
#endif
10621107

10631108
Var IntlEngineInterfaceExtensionObject::EntryIntl_GetLocaleData(RecyclableObject* function, CallInfo callInfo, ...)
@@ -1087,11 +1132,13 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
10871132
if (kind == LocaleDataKind::Collation)
10881133
{
10891134
ScopedUEnumeration collations(ucol_getKeywordValuesForLocale("collation", localeID, false, &status));
1090-
ICU_ASSERT(status, true);
1135+
int collationsCount = uenum_count(collations, &status);
1136+
1137+
// we expect collationsCount to have at least "standard" and "search" in it
1138+
ICU_ASSERT(status, collationsCount > 2);
10911139

10921140
// the return array can't include "standard" and "search", but must have its first element be null (count - 2 + 1) [#sec-intl-collator-internal-slots]
1093-
ret = library->CreateArray(uenum_count(collations, &status) - 1);
1094-
ICU_ASSERT(status, true);
1141+
ret = library->CreateArray(collationsCount - 1);
10951142
ret->SetItem(0, library->GetNull(), flag);
10961143

10971144
int collationLen = 0;
@@ -1111,7 +1158,7 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
11111158
const size_t unicodeCollationLen = strlen(unicodeCollation);
11121159

11131160
// we only need strlen(unicodeCollation) + 1 char16s because unicodeCollation will always be ASCII (funnily enough)
1114-
char16 *unicodeCollation16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, strlen(unicodeCollation) + 1);
1161+
char16 *unicodeCollation16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, unicodeCollationLen + 1);
11151162
charcount_t unicodeCollation16Len = 0;
11161163
HRESULT hr = utf8::NarrowStringToWideNoAlloc(
11171164
unicodeCollation,
@@ -1836,10 +1883,18 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
18361883
bool ignorePunctuation = AssertBooleanProperty(state, PropertyIds::ignorePunctuation);
18371884
bool numeric = AssertBooleanProperty(state, PropertyIds::numeric);
18381885
CollatorCaseFirst caseFirst = AssertEnumProperty<CollatorCaseFirst>(state, PropertyIds::caseFirstEnum);
1886+
JavascriptString *usage = AssertStringProperty(state, PropertyIds::usage);
18391887

18401888
char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
18411889
LangtagToLocaleID(langtag, localeID);
18421890

1891+
const char16 searchString[] = _u("search");
1892+
if (usage->BufferEquals(searchString, _countof(searchString) - 1)) // minus the null terminator
1893+
{
1894+
uloc_setKeywordValue("collation", "search", localeID, _countof(localeID), &status);
1895+
ICU_ASSERT(status, true);
1896+
}
1897+
18431898
coll = FinalizableUCollator::New(scriptContext->GetRecycler(), ucol_open(localeID, &status));
18441899
ICU_ASSERT(status, true);
18451900

@@ -3114,45 +3169,34 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
31143169
*/
31153170
Var IntlEngineInterfaceExtensionObject::EntryIntl_RegisterBuiltInFunction(RecyclableObject* function, CallInfo callInfo, ...)
31163171
{
3117-
// Don't put this in a header or add it to the namespace even in this file. Keep it to the minimum scope needed.
3118-
enum class IntlBuiltInFunctionID : int32 {
3119-
Min = 0,
3120-
DateToLocaleString = Min,
3121-
DateToLocaleDateString,
3122-
DateToLocaleTimeString,
3123-
NumberToLocaleString,
3124-
StringLocaleCompare,
3125-
Max
3126-
};
3127-
31283172
EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
31293173

31303174
// This function will only be used during the construction of the Intl object, hence Asserts are in place.
31313175
Assert(args.Info.Count >= 3 && JavascriptFunction::Is(args.Values[1]) && TaggedInt::Is(args.Values[2]));
31323176

31333177
JavascriptFunction *func = JavascriptFunction::FromVar(args.Values[1]);
31343178
int32 id = TaggedInt::ToInt32(args.Values[2]);
3135-
Assert(id >= (int32)IntlBuiltInFunctionID::Min && id < (int32)IntlBuiltInFunctionID::Max);
3179+
Assert(id >= 0 && id < (int32)BuiltInFunctionID::Max);
31363180

31373181
EngineInterfaceObject* nativeEngineInterfaceObj = scriptContext->GetLibrary()->GetEngineInterfaceObject();
31383182
IntlEngineInterfaceExtensionObject* extensionObject = static_cast<IntlEngineInterfaceExtensionObject*>(nativeEngineInterfaceObj->GetEngineExtension(EngineInterfaceExtensionKind_Intl));
31393183

3140-
IntlBuiltInFunctionID functionID = static_cast<IntlBuiltInFunctionID>(id);
3184+
BuiltInFunctionID functionID = static_cast<BuiltInFunctionID>(id);
31413185
switch (functionID)
31423186
{
3143-
case IntlBuiltInFunctionID::DateToLocaleString:
3187+
case BuiltInFunctionID::DateToLocaleString:
31443188
extensionObject->dateToLocaleString = func;
31453189
break;
3146-
case IntlBuiltInFunctionID::DateToLocaleDateString:
3190+
case BuiltInFunctionID::DateToLocaleDateString:
31473191
extensionObject->dateToLocaleDateString = func;
31483192
break;
3149-
case IntlBuiltInFunctionID::DateToLocaleTimeString:
3193+
case BuiltInFunctionID::DateToLocaleTimeString:
31503194
extensionObject->dateToLocaleTimeString = func;
31513195
break;
3152-
case IntlBuiltInFunctionID::NumberToLocaleString:
3196+
case BuiltInFunctionID::NumberToLocaleString:
31533197
extensionObject->numberToLocaleString = func;
31543198
break;
3155-
case IntlBuiltInFunctionID::StringLocaleCompare:
3199+
case BuiltInFunctionID::StringLocaleCompare:
31563200
extensionObject->stringLocaleCompare = func;
31573201
break;
31583202
default:

0 commit comments

Comments
 (0)