mirror of
https://github.com/Basilisk-Development-Team/UXP-mirror.git
synced 2026-11-05 17:42:42 -05:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baca5bb914 |
No files matched your search
@@ -732,6 +732,7 @@ function initializeIntlObject(obj, type, lazyData) {
|
||||
|
||||
assert((type === "Collator" && IsCollator(obj)) ||
|
||||
(type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
|
||||
(type === "ListFormat" && IsListFormat(obj) !== null) ||
|
||||
(type === "NumberFormat" && IsNumberFormat(obj)) ||
|
||||
(type === "PluralRules" && IsPluralRules(obj)) ||
|
||||
(type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
|
||||
@@ -815,6 +816,7 @@ function getIntlObjectInternals(obj) {
|
||||
assert(hasOwn("type", internals), "missing type");
|
||||
assert((internals.type === "Collator" && IsCollator(obj)) ||
|
||||
(internals.type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
|
||||
(internals.type === "ListFormat" && IsListFormat(obj)) ||
|
||||
(internals.type === "NumberFormat" && IsNumberFormat(obj)) ||
|
||||
(internals.type === "PluralRules" && IsPluralRules(obj)) ||
|
||||
(internals.type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
|
||||
@@ -850,6 +852,9 @@ function getInternals(obj) {
|
||||
case "DateTimeFormat":
|
||||
internalProps = resolveDateTimeFormatInternals(internals.lazyData);
|
||||
break;
|
||||
case "ListFormat":
|
||||
internalProps = resolveListFormatInternals(internals.lazyData);
|
||||
break;
|
||||
case "PluralRules":
|
||||
internalProps = resolvePluralRulesInternals(internals.lazyData);
|
||||
break;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/LanguageTag.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "builtin/intl/ICUHeader.h"
|
||||
#include "builtin/intl/Locale.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
@@ -523,6 +524,8 @@ js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp)
|
||||
kind = SupportedLocaleKind::Collator;
|
||||
} else if (StringEqualsAscii(typeStr, "DateTimeFormat")) {
|
||||
kind = SupportedLocaleKind::DateTimeFormat;
|
||||
} else if (StringEqualsAscii(typeStr, "ListFormat")) {
|
||||
kind = SupportedLocaleKind::ListFormat;
|
||||
} else if (StringEqualsAscii(typeStr, "NumberFormat")) {
|
||||
kind = SupportedLocaleKind::NumberFormat;
|
||||
} else if (StringEqualsAscii(typeStr, "PluralRules")) {
|
||||
@@ -649,12 +652,12 @@ js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc, Value* vp)
|
||||
// That implies we must ignore any candidate which isn't supported by all Intl
|
||||
// service constructors.
|
||||
//
|
||||
// Note: We don't test the supported locales of either Intl.PluralRules or
|
||||
// Intl.RelativeTimeFormat, because ICU doesn't provide the necessary API to
|
||||
// return actual set of supported locales for these constructors. Instead it
|
||||
// returns the complete set of available locales for ULocale, which is a
|
||||
// superset of the locales supported by Collator, NumberFormat, and
|
||||
// DateTimeFormat.
|
||||
// Note: We don't test the supported locales of either Intl.ListFormat,
|
||||
// Intl.PluralRules, Intl.RelativeTimeFormat, because ICU doesn't provide the
|
||||
// necessary API to return actual set of supported locales for these
|
||||
// constructors. Instead it returns the complete set of available locales for
|
||||
// ULocale, which is a superset of the locales supported by Collator,
|
||||
// NumberFormat, and DateTimeFormat.
|
||||
bool isSupported = true;
|
||||
for (auto kind : {SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
|
||||
SupportedLocaleKind::NumberFormat}) {
|
||||
|
||||
@@ -0,0 +1,501 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/intl/ScopedICUObject.h"
|
||||
#include "js/Utility.h"
|
||||
#include "js/Vector.h"
|
||||
#include "unicode/uformattedvalue.h"
|
||||
#include "unicode/ulistformatter.h"
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#include "jscntxt.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/SelfHosting.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
using mozilla::AssertedCast;
|
||||
|
||||
using js::intl::CallICU;
|
||||
using js::intl::IcuLocale;
|
||||
|
||||
const ClassOps ListFormatObject::classOps_ = {nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
ListFormatObject::finalize};
|
||||
const Class ListFormatObject::class_ = {
|
||||
js_Object_str,
|
||||
JSCLASS_HAS_RESERVED_SLOTS(ListFormatObject::SLOT_COUNT) |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_ListFormat) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&ListFormatObject::classOps_
|
||||
};
|
||||
|
||||
const Class& ListFormatObject::protoClass_ = PlainObject::class_;
|
||||
|
||||
#if JS_HAS_TOSOURCE
|
||||
static bool
|
||||
listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setString(cx->names().ListFormat);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const JSFunctionSpec listFormat_static_methods[] = {
|
||||
JS_SELF_HOSTED_FN("supportedLocalesOf",
|
||||
"Intl_ListFormat_supportedLocalesOf", 1, 0),
|
||||
JS_FS_END};
|
||||
|
||||
static const JSFunctionSpec listFormat_methods[] = {
|
||||
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0,
|
||||
0),
|
||||
JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0),
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
JS_SELF_HOSTED_FN("formatToParts", "Intl_ListFormat_formatToParts", 1, 0),
|
||||
#endif
|
||||
#if JS_HAS_TOSOURCE
|
||||
JS_FN(js_toSource_str, listFormat_toSource, 0, 0),
|
||||
#endif
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
static const JSPropertySpec listFormat_properties[] = {
|
||||
JS_STRING_SYM_PS(toStringTag, "Intl.ListFormat", JSPROP_READONLY),
|
||||
JS_PS_END};
|
||||
|
||||
static JSObject*
|
||||
CreateListFormatPrototype(JSContext* cx, JSProtoKey key) {
|
||||
return GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global());
|
||||
}
|
||||
|
||||
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
const ClassSpec ListFormatObject::classSpec_ = {
|
||||
GenericCreateConstructor<ListFormat, 0, gc::AllocKind::FUNCTION>,
|
||||
CreateListFormatPrototype,
|
||||
listFormat_static_methods,
|
||||
nullptr,
|
||||
listFormat_methods,
|
||||
listFormat_properties
|
||||
// nullptr,
|
||||
// ClassSpec::DontDefineConstructor
|
||||
};
|
||||
|
||||
/**
|
||||
* Intl.ListFormat([ locales [, options]])
|
||||
*/
|
||||
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
if (!ThrowIfNotConstructing(cx, args, "Intl.ListFormat")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
|
||||
RootedObject proto(cx);
|
||||
if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ListFormatObject*> listFormat(
|
||||
cx, NewObjectWithClassProto<ListFormatObject>(cx, proto));
|
||||
if (!listFormat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleValue locales = args.get(0);
|
||||
HandleValue options = args.get(1);
|
||||
|
||||
// Step 3.
|
||||
if (!intl::InitializeObject(cx, listFormat, cx->names().InitializeListFormat,
|
||||
locales, options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*listFormat);
|
||||
return true;
|
||||
}
|
||||
|
||||
void js::ListFormatObject::finalize(FreeOp* fop, JSObject* obj) {
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
|
||||
if (UListFormatter* lf = obj->as<ListFormatObject>().getListFormatter()) {
|
||||
ulistfmt_close(lf);
|
||||
}
|
||||
}
|
||||
|
||||
bool js::AddListFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl) {
|
||||
JSObject* ctor = GlobalObject::createConstructor(cx, ListFormat, cx->names().ListFormat, 0);
|
||||
if (!ctor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue ctorValue(cx, ObjectValue(*ctor));
|
||||
return DefineProperty(cx, intl, cx->names().ListFormat, ctorValue, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new UListFormatter with the locale and list formatting options
|
||||
* of the given ListFormat.
|
||||
*/
|
||||
static UListFormatter* NewUListFormatter(JSContext* cx,
|
||||
Handle<ListFormatObject*> listFormat) {
|
||||
RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat));
|
||||
if (!internals) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue value(cx);
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
JSAutoByteString bytes;
|
||||
char* locale = bytes.encodeLatin1(cx, value.toString());
|
||||
if (!locale) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
enum class ListFormatType { Conjunction, Disjunction, Unit };
|
||||
|
||||
ListFormatType type;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
JSLinearString* strType = value.toString()->ensureLinear(cx);
|
||||
if (!strType) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(strType, "conjunction")) {
|
||||
type = ListFormatType::Conjunction;
|
||||
} else if (StringEqualsAscii(strType, "disjunction")) {
|
||||
type = ListFormatType::Disjunction;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(strType, "unit"));
|
||||
type = ListFormatType::Unit;
|
||||
}
|
||||
}
|
||||
|
||||
enum class ListFormatStyle { Long, Short, Narrow };
|
||||
|
||||
ListFormatStyle style;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
JSLinearString* strStyle = value.toString()->ensureLinear(cx);
|
||||
if (!strStyle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(strStyle, "long")) {
|
||||
style = ListFormatStyle::Long;
|
||||
} else if (StringEqualsAscii(strStyle, "short")) {
|
||||
style = ListFormatStyle::Short;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(strStyle, "narrow"));
|
||||
style = ListFormatStyle::Narrow;
|
||||
}
|
||||
}
|
||||
|
||||
// We're currently only supporting "conjunctive-long" list formatters due to
|
||||
// missing ICU APIs: https://unicode-org.atlassian.net/browse/ICU-12863
|
||||
MOZ_ASSERT(type == ListFormatType::Conjunction);
|
||||
MOZ_ASSERT(style == ListFormatStyle::Long);
|
||||
|
||||
mozilla::Unused << type;
|
||||
mozilla::Unused << style;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UListFormatter* lf = ulistfmt_open(IcuLocale(locale), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
return lf;
|
||||
}
|
||||
|
||||
static constexpr size_t DEFAULT_LIST_LENGTH = 8;
|
||||
|
||||
using ListFormatStringVector = Vector<UniqueTwoByteChars, DEFAULT_LIST_LENGTH>;
|
||||
using ListFormatStringLengthVector = Vector<int32_t, DEFAULT_LIST_LENGTH>;
|
||||
|
||||
static_assert(sizeof(UniqueTwoByteChars) == sizeof(char16_t*),
|
||||
"UniqueTwoByteChars are stored efficiently and are held in "
|
||||
"continuous memory");
|
||||
|
||||
/**
|
||||
* FormatList ( listFormat, list )
|
||||
*/
|
||||
static bool FormatList(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
JSString* str = intl::CallICU(cx, [lf, &strings, &stringLengths](
|
||||
UChar* chars, int32_t size,
|
||||
UErrorCode* status) {
|
||||
return ulistfmt_format(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), chars, size, status);
|
||||
});
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result.setString(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
static JSString* FormattedValueToString(JSContext* cx,
|
||||
const UFormattedValue* formattedValue) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t strLength;
|
||||
const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return NewStringCopyN<CanGC>(cx, str, AssertedCast<uint32_t>(strLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* FormatListToParts ( listFormat, list )
|
||||
*/
|
||||
static bool FormatListToParts(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UFormattedList* formatted = ulistfmt_openResult(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UFormattedList, ulistfmt_closeResult> toClose(formatted);
|
||||
|
||||
ulistfmt_formatStringsToResult(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
const UFormattedValue* formattedValue =
|
||||
ulistfmt_resultAsValue(formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedString overallResult(cx, FormattedValueToString(cx, formattedValue));
|
||||
if (!overallResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
|
||||
if (!partsArray) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
|
||||
|
||||
size_t lastEndIndex = 0;
|
||||
RootedObject singlePart(cx);
|
||||
RootedValue partType(cx);
|
||||
RootedValue val(cx);
|
||||
|
||||
auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
|
||||
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
|
||||
if (!singlePart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
partType = StringValue(cx->names().*type);
|
||||
if (!DefineProperty(cx, singlePart, cx->names().type, partType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString* partSubstr = NewDependentString(
|
||||
cx, overallResult, beginIndex, endIndex - beginIndex);
|
||||
if (!partSubstr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val = StringValue(partSubstr);
|
||||
if (!DefineProperty(cx, singlePart, cx->names().value, val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastEndIndex = endIndex;
|
||||
return true;
|
||||
};
|
||||
|
||||
UConstrainedFieldPosition* fpos = ucfpos_open(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
|
||||
|
||||
// We're only interested in ULISTFMT_ELEMENT_FIELD fields.
|
||||
ucfpos_constrainField(fpos, UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD,
|
||||
&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
if (!hasMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t beginIndexInt, endIndexInt;
|
||||
ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(beginIndexInt >= 0);
|
||||
MOZ_ASSERT(endIndexInt >= 0);
|
||||
MOZ_ASSERT(beginIndexInt <= endIndexInt,
|
||||
"field iterator returning invalid range");
|
||||
|
||||
size_t beginIndex = size_t(beginIndexInt);
|
||||
size_t endIndex = size_t(endIndexInt);
|
||||
|
||||
// Indices are guaranteed to be returned in order (from left to right).
|
||||
MOZ_ASSERT(lastEndIndex <= beginIndex,
|
||||
"field iteration didn't return fields in order start to "
|
||||
"finish as expected");
|
||||
|
||||
if (lastEndIndex < beginIndex) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AppendPart(&JSAtomState::element, beginIndex, endIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Append any final literal.
|
||||
if (lastEndIndex < overallResult->length()) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex,
|
||||
overallResult->length())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result.setObject(*partsArray);
|
||||
return true;
|
||||
}
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
|
||||
Rooted<ListFormatObject*> listFormat(
|
||||
cx, &args[0].toObject().as<ListFormatObject>());
|
||||
|
||||
bool formatToParts = args[2].toBoolean();
|
||||
|
||||
// Obtain a cached UListFormatter object.
|
||||
UListFormatter* lf = listFormat->getListFormatter();
|
||||
if (!lf) {
|
||||
lf = NewUListFormatter(cx, listFormat);
|
||||
if (!lf) {
|
||||
return false;
|
||||
}
|
||||
listFormat->setListFormatter(lf);
|
||||
}
|
||||
|
||||
// Collect all strings and their lengths.
|
||||
ListFormatStringVector strings(cx);
|
||||
ListFormatStringLengthVector stringLengths(cx);
|
||||
|
||||
RootedArrayObject list(cx, &args[1].toObject().as<ArrayObject>());
|
||||
RootedValue value(cx);
|
||||
for (uint32_t i = 0; i < list->length(); i++) {
|
||||
if (!GetElement(cx, list, list, i, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString* linear = value.toString()->ensureLinear(cx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t linearLength = linear->length();
|
||||
if (!stringLengths.append(linearLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength);
|
||||
if (!chars) {
|
||||
return false;
|
||||
}
|
||||
CopyChars(chars.get(), *linear);
|
||||
|
||||
if (!strings.append(std::move(chars))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the UListFormatter to actually format the strings.
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
if (formatToParts) {
|
||||
return FormatListToParts(cx, lf, strings, stringLengths, args.rval());
|
||||
}
|
||||
#else
|
||||
MOZ_ASSERT(!formatToParts);
|
||||
#endif
|
||||
|
||||
return FormatList(cx, lf, strings, stringLengths, args.rval());
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef builtin_intl_ListFormat_h
|
||||
#define builtin_intl_ListFormat_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "js/Class.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
struct UListFormatter;
|
||||
|
||||
namespace js {
|
||||
|
||||
class ListFormatObject : public NativeObject {
|
||||
public:
|
||||
static const Class class_;
|
||||
static const Class& protoClass_;
|
||||
|
||||
static constexpr uint32_t INTERNALS_SLOT = 0;
|
||||
static constexpr uint32_t ULIST_FORMATTER_SLOT = 1;
|
||||
static constexpr uint32_t SLOT_COUNT = 2;
|
||||
|
||||
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
|
||||
"INTERNALS_SLOT must match self-hosting define for internals "
|
||||
"object slot");
|
||||
|
||||
UListFormatter* getListFormatter() const {
|
||||
const auto& slot = getFixedSlot(ULIST_FORMATTER_SLOT);
|
||||
if (slot.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<UListFormatter*>(slot.toPrivate());
|
||||
}
|
||||
|
||||
void setListFormatter(UListFormatter* formatter) {
|
||||
setFixedSlot(ULIST_FORMATTER_SLOT, PrivateValue(formatter));
|
||||
}
|
||||
|
||||
private:
|
||||
static const ClassOps classOps_;
|
||||
static const ClassSpec classSpec_;
|
||||
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representing the array of string values |list| according to
|
||||
* the effective locale and the formatting options of the given ListFormat.
|
||||
*
|
||||
* Usage: formatted = intl_FormatList(listFormat, list, formatToParts)
|
||||
*/
|
||||
extern bool intl_FormatList(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_intl_ListFormat_h */
|
||||
@@ -0,0 +1,262 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* ListFormat internal properties.
|
||||
*/
|
||||
var listFormatInternalProperties = {
|
||||
localeData: function() // eslint-disable-line object-shorthand
|
||||
{
|
||||
// ListFormat don't support any extension keys.
|
||||
return {};
|
||||
},
|
||||
relevantExtensionKeys: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Intl.ListFormat ( [ locales [ , options ] ] )
|
||||
*
|
||||
* Compute an internal properties object from |lazyListFormatData|.
|
||||
*/
|
||||
function resolveListFormatInternals(lazyListFormatData) {
|
||||
assert(IsObject(lazyListFormatData), "lazy data not an object?");
|
||||
|
||||
var internalProps = std_Object_create(null);
|
||||
|
||||
var ListFormat = listFormatInternalProperties;
|
||||
|
||||
// Compute effective locale.
|
||||
|
||||
// Step 9.
|
||||
var localeData = ListFormat.localeData;
|
||||
|
||||
// Step 10.
|
||||
var r = ResolveLocale("ListFormat",
|
||||
lazyListFormatData.requestedLocales,
|
||||
lazyListFormatData.opt,
|
||||
ListFormat.relevantExtensionKeys,
|
||||
localeData);
|
||||
|
||||
// Step 11.
|
||||
internalProps.locale = r.locale;
|
||||
|
||||
// Step 13.
|
||||
internalProps.type = lazyListFormatData.type;
|
||||
|
||||
// Step 15.
|
||||
internalProps.style = lazyListFormatData.style;
|
||||
|
||||
// Steps 16-23 (not applicable in our implementation).
|
||||
|
||||
// The caller is responsible for associating |internalProps| with the right
|
||||
// object using |setInternalProperties|.
|
||||
return internalProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing the ListFormat internal properties of |obj|.
|
||||
*/
|
||||
function getListFormatInternals(obj) {
|
||||
assert(IsObject(obj), "getListFormatInternals called with non-object");
|
||||
assert(GuardToListFormat(obj) !== null, "getListFormatInternals called with non-ListFormat");
|
||||
|
||||
var internals = getIntlObjectInternals(obj);
|
||||
assert(internals.type === "ListFormat", "bad type escaped getIntlObjectInternals");
|
||||
|
||||
// If internal properties have already been computed, use them.
|
||||
var internalProps = maybeInternalProperties(internals);
|
||||
if (internalProps)
|
||||
return internalProps;
|
||||
|
||||
// Otherwise it's time to fully create them.
|
||||
internalProps = resolveListFormatInternals(internals.lazyData);
|
||||
setInternalProperties(internals, internalProps);
|
||||
return internalProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat ( [ locales [ , options ] ] )
|
||||
*
|
||||
* Initializes an object as a ListFormat.
|
||||
*
|
||||
* This method is complicated a moderate bit by its implementing initialization
|
||||
* as a *lazy* concept. Everything that must happen now, does -- but we defer
|
||||
* all the work we can until the object is actually used as a ListFormat.
|
||||
* This later work occurs in |resolveListFormatInternals|; steps not noted
|
||||
* here occur there.
|
||||
*/
|
||||
function InitializeListFormat(listFormat, locales, options) {
|
||||
assert(IsObject(listFormat), "InitializeListFormat called with non-object");
|
||||
assert(GuardToListFormat(listFormat) !== null, "InitializeListFormat called with non-ListFormat");
|
||||
|
||||
// Lazy ListFormat data has the following structure:
|
||||
//
|
||||
// {
|
||||
// requestedLocales: List of locales,
|
||||
// type: "conjunction" / "disjunction" / "unit",
|
||||
// style: "long" / "short" / "narrow",
|
||||
//
|
||||
// opt: // opt object computed in InitializeListFormat
|
||||
// {
|
||||
// localeMatcher: "lookup" / "best fit",
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Note that lazy data is only installed as a final step of initialization,
|
||||
// so every ListFormat lazy data object has *all* these properties, never a
|
||||
// subset of them.
|
||||
var lazyListFormatData = std_Object_create(null);
|
||||
|
||||
// Step 3.
|
||||
var requestedLocales = CanonicalizeLocaleList(locales);
|
||||
lazyListFormatData.requestedLocales = requestedLocales;
|
||||
|
||||
// Steps 4-5.
|
||||
if (options === undefined)
|
||||
options = std_Object_create(null);
|
||||
else
|
||||
options = ToObject(options);
|
||||
|
||||
// Step 6.
|
||||
var opt = new Record();
|
||||
lazyListFormatData.opt = opt;
|
||||
|
||||
// Steps 7-8.
|
||||
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
|
||||
opt.localeMatcher = matcher;
|
||||
|
||||
// Compute formatting options.
|
||||
|
||||
// We're currently only supporting "conjunctive-long" list formatters due to
|
||||
// missing ICU APIs: https://unicode-org.atlassian.net/browse/ICU-12863
|
||||
|
||||
// Steps 12-13.
|
||||
var type = GetOption(options, "type", "string", ["conjunction"], "conjunction");
|
||||
lazyListFormatData.type = type;
|
||||
|
||||
// Steps 14-15.
|
||||
var style = GetOption(options, "style", "string", ["long"], "long");
|
||||
lazyListFormatData.style = style;
|
||||
|
||||
// We've done everything that must be done now: mark the lazy data as fully
|
||||
// computed and install it.
|
||||
initializeIntlObject(listFormat, "ListFormat", lazyListFormatData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subset of the given locale list for which this locale list has a
|
||||
* matching (possibly fallback) locale. Locales appear in the same order in the
|
||||
* returned list as in the input list.
|
||||
*/
|
||||
function Intl_ListFormat_supportedLocalesOf(locales /*, options*/) {
|
||||
var options = arguments.length > 1 ? arguments[1] : undefined;
|
||||
|
||||
// Step 1.
|
||||
var availableLocales = "ListFormat";
|
||||
|
||||
// Step 2.
|
||||
var requestedLocales = CanonicalizeLocaleList(locales);
|
||||
|
||||
// Step 3.
|
||||
return SupportedLocales(availableLocales, requestedLocales, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* StringListFromIterable ( iterable )
|
||||
*/
|
||||
function StringListFromIterable(iterable, methodName) {
|
||||
// Step 1.
|
||||
if (iterable === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
var list = [];
|
||||
|
||||
// Steps 2, 4-5.
|
||||
for (var element of allowContentIter(iterable)) {
|
||||
// Step 5.b.ii.
|
||||
if (typeof element !== "string") {
|
||||
ThrowTypeError(JSMSG_NOT_EXPECTED_TYPE, methodName, "string", typeof element);
|
||||
}
|
||||
|
||||
// Step 5.b.iii.
|
||||
_DefineDataProperty(list, list.length, element);
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat.prototype.format ( list )
|
||||
*/
|
||||
function Intl_ListFormat_format(list) {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this, list,
|
||||
"Intl_ListFormat_format");
|
||||
}
|
||||
|
||||
// Ensure the ListFormat internals are resolved.
|
||||
getListFormatInternals(listFormat);
|
||||
|
||||
// Step 4.
|
||||
var stringList = StringListFromIterable(list, "format");
|
||||
|
||||
// Step 5.
|
||||
return intl_FormatList(listFormat, stringList, /* formatToParts = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat.prototype.formatToParts ( list )
|
||||
*/
|
||||
function Intl_ListFormat_formatToParts(list) {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this, list,
|
||||
"Intl_ListFormat_formatToParts");
|
||||
}
|
||||
|
||||
// Ensure the ListFormat internals are resolved.
|
||||
getListFormatInternals(listFormat);
|
||||
|
||||
// Step 4.
|
||||
var stringList = StringListFromIterable(list, "formatToParts");
|
||||
|
||||
// Step 5.
|
||||
return intl_FormatList(listFormat, stringList, /* formatToParts = */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolved options for a ListFormat object.
|
||||
*/
|
||||
function Intl_ListFormat_resolvedOptions() {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this,
|
||||
"Intl_ListFormat_resolvedOptions");
|
||||
}
|
||||
|
||||
var internals = getListFormatInternals(listFormat);
|
||||
|
||||
// Steps 4-5.
|
||||
var result = {
|
||||
locale: internals.locale,
|
||||
type: internals.type,
|
||||
style: internals.style,
|
||||
};
|
||||
|
||||
// Step 6.
|
||||
return result;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,285 +1,286 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef builtin_intl_SharedIntlData_h
|
||||
#define builtin_intl_SharedIntlData_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jsalloc.h"
|
||||
#include "js/CharacterEncoding.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/GCHashTable.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/Utility.h"
|
||||
#include "vm/String.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
namespace intl {
|
||||
|
||||
/**
|
||||
* Stores Intl data which can be shared across compartments (but not contexts).
|
||||
*
|
||||
* Used for data which is expensive when computed repeatedly or is not
|
||||
* available through ICU.
|
||||
*/
|
||||
class SharedIntlData
|
||||
{
|
||||
struct LinearStringLookup
|
||||
{
|
||||
union {
|
||||
const JS::Latin1Char* latin1Chars;
|
||||
const char16_t* twoByteChars;
|
||||
};
|
||||
bool isLatin1;
|
||||
size_t length;
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
HashNumber hash = 0;
|
||||
|
||||
explicit LinearStringLookup(JSLinearString* string)
|
||||
: isLatin1(string->hasLatin1Chars()), length(string->length())
|
||||
{
|
||||
if (isLatin1)
|
||||
latin1Chars = string->latin1Chars(nogc);
|
||||
else
|
||||
twoByteChars = string->twoByteChars(nogc);
|
||||
}
|
||||
|
||||
LinearStringLookup(const char* chars, size_t length)
|
||||
: isLatin1(true), length(length)
|
||||
{
|
||||
latin1Chars = reinterpret_cast<const JS::Latin1Char*>(chars);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Information tracking the set of the supported time zone names, derived
|
||||
* from the IANA time zone database <https://www.iana.org/time-zones>.
|
||||
*
|
||||
* There are two kinds of IANA time zone names: Zone and Link (denoted as
|
||||
* such in database source files). Zone names are the canonical, preferred
|
||||
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
|
||||
* target Zone names for their meaning, e.g. Asia/Calcutta targets
|
||||
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
|
||||
* sense of deprecation: some Link names also exist partly for convenience,
|
||||
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
|
||||
*
|
||||
* Two data sources determine the time zone names we support: those ICU
|
||||
* supports and IANA's zone information.
|
||||
*
|
||||
* Unfortunately the names ICU and IANA support, and their Link
|
||||
* relationships from name to target, aren't identical, so we can't simply
|
||||
* implicitly trust ICU's name handling. We must perform various
|
||||
* preprocessing of user-provided zone names and post-processing of
|
||||
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
|
||||
*
|
||||
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
|
||||
* <http://unicode.org/cldr/trac/ticket/9892>.
|
||||
*/
|
||||
|
||||
using TimeZoneName = JSAtom*;
|
||||
|
||||
struct TimeZoneHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSFlatString* timeZone);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(TimeZoneName key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using TimeZoneSet = js::GCHashSet<TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
using TimeZoneMap = js::GCHashMap<TimeZoneName,
|
||||
TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
/**
|
||||
* As a threshold matter, available time zones are those time zones ICU
|
||||
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
|
||||
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
|
||||
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
|
||||
* compatibility purposes. This set consists of ICU's supported time zones,
|
||||
* minus all backwards-compatibility time zones.
|
||||
*/
|
||||
TimeZoneSet availableTimeZones;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Zones, that ICU instead treats as
|
||||
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
|
||||
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
|
||||
* considers the former a Link that targets "America/Indianapolis" (which
|
||||
* IANA treats as a Link).
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name in this set, we must *not* return ICU's
|
||||
* canonicalization.
|
||||
*/
|
||||
TimeZoneSet ianaZonesTreatedAsLinksByICU;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Links to one target, that ICU
|
||||
* instead treats as either Zones, or Links to different targets. An
|
||||
* example of the former is "Asia/Calcutta, which IANA assigns the target
|
||||
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
|
||||
* is "America/Virgin", which IANA assigns the target
|
||||
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name that's a key in this map, we *must* return
|
||||
* the corresponding value and *must not* return ICU's canonicalization.
|
||||
*/
|
||||
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
|
||||
|
||||
bool timeZoneDataInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available time zone names, because it's too expensive to
|
||||
* call ucal_openTimeZones() repeatedly.
|
||||
*/
|
||||
bool ensureTimeZones(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the validated time zone name in |result|. If the input time zone
|
||||
* isn't a valid IANA time zone name, |result| remains unchanged.
|
||||
*/
|
||||
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
|
||||
/**
|
||||
* Returns the canonical time zone name in |result|. If no canonical name
|
||||
* was found, |result| remains unchanged.
|
||||
*
|
||||
* This method only handles time zones which are canonicalized differently
|
||||
* by ICU when compared to IANA.
|
||||
*/
|
||||
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
private:
|
||||
using Locale = JSAtom*;
|
||||
|
||||
struct LocaleHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSLinearString* locale);
|
||||
Lookup(const char* chars, size_t length);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(Locale key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using LocaleSet = GCHashSet<Locale, LocaleHasher, SystemAllocPolicy>;
|
||||
|
||||
// Set of supported locales for all Intl service constructors except Collator,
|
||||
// which uses its own set.
|
||||
//
|
||||
// UDateFormat:
|
||||
// udat_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UNumberFormatter:
|
||||
// unum_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UPluralRules and URelativeDateTimeFormatter:
|
||||
// We're going to use ULocale availableLocales as per ICU recommendation:
|
||||
// https://unicode-org.atlassian.net/browse/ICU-12756
|
||||
LocaleSet supportedLocales;
|
||||
|
||||
// ucol_[count,get]Available() return different results compared to
|
||||
// uloc_[count,get]Available(), we can't use |supportedLocales| here.
|
||||
LocaleSet collatorSupportedLocales;
|
||||
|
||||
bool supportedLocalesInitialized = false;
|
||||
|
||||
// CountAvailable and GetAvailable describe the signatures used for ICU API
|
||||
// to determine available locales for various functionality.
|
||||
using CountAvailable = int32_t (*)();
|
||||
using GetAvailable = const char* (*)(int32_t localeIndex);
|
||||
|
||||
static bool getAvailableLocales(JSContext* cx, LocaleSet& locales,
|
||||
CountAvailable countAvailable,
|
||||
GetAvailable getAvailable);
|
||||
|
||||
/**
|
||||
* Precomputes the available locales sets.
|
||||
*/
|
||||
bool ensureSupportedLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
enum class SupportedLocaleKind {
|
||||
Collator,
|
||||
DateTimeFormat,
|
||||
NumberFormat,
|
||||
PluralRules,
|
||||
RelativeTimeFormat
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets |supported| to true if |locale| is supported by the requested Intl
|
||||
* service constructor. Otherwise sets |supported| to false.
|
||||
*/
|
||||
[[nodiscard]] bool isSupportedLocale(JSContext* cx, SupportedLocaleKind kind,
|
||||
JS::Handle<JSString*> locale,
|
||||
bool* supported);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The case first parameter (BCP47 key "kf") allows to switch the order of
|
||||
* upper- and lower-case characters. ICU doesn't directly provide an API
|
||||
* to query the default case first value of a given locale, but instead
|
||||
* requires to instantiate a collator object and then query the case first
|
||||
* attribute (UCOL_CASE_FIRST).
|
||||
* To avoid instantiating an additional collator object whenever we need
|
||||
* to retrieve the default case first value of a specific locale, we
|
||||
* compute the default case first value for every supported locale only
|
||||
* once and then keep a list of all locales which don't use the default
|
||||
* case first setting.
|
||||
* There is almost no difference between lower-case first and when case
|
||||
* first is disabled (UCOL_LOWER_FIRST resp. UCOL_OFF), so we only need to
|
||||
* track locales which use upper-case first as their default setting.
|
||||
*/
|
||||
|
||||
LocaleSet upperCaseFirstLocales;
|
||||
|
||||
bool upperCaseFirstInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available locales which use upper-case first sorting.
|
||||
*/
|
||||
bool ensureUpperCaseFirstLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets |isUpperFirst| to true if |locale| sorts upper-case characters
|
||||
* before lower-case characters.
|
||||
*/
|
||||
bool isUpperCaseFirst(JSContext* cx, JS::HandleString locale, bool* isUpperFirst);
|
||||
|
||||
public:
|
||||
void destroyInstance();
|
||||
|
||||
void trace(JSTracer* trc);
|
||||
|
||||
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
||||
};
|
||||
|
||||
} // namespace intl
|
||||
|
||||
} // namespace js
|
||||
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef builtin_intl_SharedIntlData_h
|
||||
#define builtin_intl_SharedIntlData_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jsalloc.h"
|
||||
#include "js/CharacterEncoding.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/GCHashTable.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/Utility.h"
|
||||
#include "vm/String.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
namespace intl {
|
||||
|
||||
/**
|
||||
* Stores Intl data which can be shared across compartments (but not contexts).
|
||||
*
|
||||
* Used for data which is expensive when computed repeatedly or is not
|
||||
* available through ICU.
|
||||
*/
|
||||
class SharedIntlData
|
||||
{
|
||||
struct LinearStringLookup
|
||||
{
|
||||
union {
|
||||
const JS::Latin1Char* latin1Chars;
|
||||
const char16_t* twoByteChars;
|
||||
};
|
||||
bool isLatin1;
|
||||
size_t length;
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
HashNumber hash = 0;
|
||||
|
||||
explicit LinearStringLookup(JSLinearString* string)
|
||||
: isLatin1(string->hasLatin1Chars()), length(string->length())
|
||||
{
|
||||
if (isLatin1)
|
||||
latin1Chars = string->latin1Chars(nogc);
|
||||
else
|
||||
twoByteChars = string->twoByteChars(nogc);
|
||||
}
|
||||
|
||||
LinearStringLookup(const char* chars, size_t length)
|
||||
: isLatin1(true), length(length)
|
||||
{
|
||||
latin1Chars = reinterpret_cast<const JS::Latin1Char*>(chars);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Information tracking the set of the supported time zone names, derived
|
||||
* from the IANA time zone database <https://www.iana.org/time-zones>.
|
||||
*
|
||||
* There are two kinds of IANA time zone names: Zone and Link (denoted as
|
||||
* such in database source files). Zone names are the canonical, preferred
|
||||
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
|
||||
* target Zone names for their meaning, e.g. Asia/Calcutta targets
|
||||
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
|
||||
* sense of deprecation: some Link names also exist partly for convenience,
|
||||
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
|
||||
*
|
||||
* Two data sources determine the time zone names we support: those ICU
|
||||
* supports and IANA's zone information.
|
||||
*
|
||||
* Unfortunately the names ICU and IANA support, and their Link
|
||||
* relationships from name to target, aren't identical, so we can't simply
|
||||
* implicitly trust ICU's name handling. We must perform various
|
||||
* preprocessing of user-provided zone names and post-processing of
|
||||
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
|
||||
*
|
||||
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
|
||||
* <http://unicode.org/cldr/trac/ticket/9892>.
|
||||
*/
|
||||
|
||||
using TimeZoneName = JSAtom*;
|
||||
|
||||
struct TimeZoneHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSFlatString* timeZone);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(TimeZoneName key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using TimeZoneSet = js::GCHashSet<TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
using TimeZoneMap = js::GCHashMap<TimeZoneName,
|
||||
TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
/**
|
||||
* As a threshold matter, available time zones are those time zones ICU
|
||||
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
|
||||
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
|
||||
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
|
||||
* compatibility purposes. This set consists of ICU's supported time zones,
|
||||
* minus all backwards-compatibility time zones.
|
||||
*/
|
||||
TimeZoneSet availableTimeZones;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Zones, that ICU instead treats as
|
||||
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
|
||||
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
|
||||
* considers the former a Link that targets "America/Indianapolis" (which
|
||||
* IANA treats as a Link).
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name in this set, we must *not* return ICU's
|
||||
* canonicalization.
|
||||
*/
|
||||
TimeZoneSet ianaZonesTreatedAsLinksByICU;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Links to one target, that ICU
|
||||
* instead treats as either Zones, or Links to different targets. An
|
||||
* example of the former is "Asia/Calcutta, which IANA assigns the target
|
||||
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
|
||||
* is "America/Virgin", which IANA assigns the target
|
||||
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name that's a key in this map, we *must* return
|
||||
* the corresponding value and *must not* return ICU's canonicalization.
|
||||
*/
|
||||
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
|
||||
|
||||
bool timeZoneDataInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available time zone names, because it's too expensive to
|
||||
* call ucal_openTimeZones() repeatedly.
|
||||
*/
|
||||
bool ensureTimeZones(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the validated time zone name in |result|. If the input time zone
|
||||
* isn't a valid IANA time zone name, |result| remains unchanged.
|
||||
*/
|
||||
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
|
||||
/**
|
||||
* Returns the canonical time zone name in |result|. If no canonical name
|
||||
* was found, |result| remains unchanged.
|
||||
*
|
||||
* This method only handles time zones which are canonicalized differently
|
||||
* by ICU when compared to IANA.
|
||||
*/
|
||||
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
private:
|
||||
using Locale = JSAtom*;
|
||||
|
||||
struct LocaleHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSLinearString* locale);
|
||||
Lookup(const char* chars, size_t length);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(Locale key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using LocaleSet = GCHashSet<Locale, LocaleHasher, SystemAllocPolicy>;
|
||||
|
||||
// Set of supported locales for all Intl service constructors except Collator,
|
||||
// which uses its own set.
|
||||
//
|
||||
// UDateFormat:
|
||||
// udat_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UNumberFormatter:
|
||||
// unum_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UListFormatter, UPluralRules and URelativeDateTimeFormatter:
|
||||
// We're going to use ULocale availableLocales as per ICU recommendation:
|
||||
// https://unicode-org.atlassian.net/browse/ICU-12756
|
||||
LocaleSet supportedLocales;
|
||||
|
||||
// ucol_[count,get]Available() return different results compared to
|
||||
// uloc_[count,get]Available(), we can't use |supportedLocales| here.
|
||||
LocaleSet collatorSupportedLocales;
|
||||
|
||||
bool supportedLocalesInitialized = false;
|
||||
|
||||
// CountAvailable and GetAvailable describe the signatures used for ICU API
|
||||
// to determine available locales for various functionality.
|
||||
using CountAvailable = int32_t (*)();
|
||||
using GetAvailable = const char* (*)(int32_t localeIndex);
|
||||
|
||||
static bool getAvailableLocales(JSContext* cx, LocaleSet& locales,
|
||||
CountAvailable countAvailable,
|
||||
GetAvailable getAvailable);
|
||||
|
||||
/**
|
||||
* Precomputes the available locales sets.
|
||||
*/
|
||||
bool ensureSupportedLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
enum class SupportedLocaleKind {
|
||||
Collator,
|
||||
DateTimeFormat,
|
||||
ListFormat,
|
||||
NumberFormat,
|
||||
PluralRules,
|
||||
RelativeTimeFormat
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets |supported| to true if |locale| is supported by the requested Intl
|
||||
* service constructor. Otherwise sets |supported| to false.
|
||||
*/
|
||||
[[nodiscard]] bool isSupportedLocale(JSContext* cx, SupportedLocaleKind kind,
|
||||
JS::Handle<JSString*> locale,
|
||||
bool* supported);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The case first parameter (BCP47 key "kf") allows to switch the order of
|
||||
* upper- and lower-case characters. ICU doesn't directly provide an API
|
||||
* to query the default case first value of a given locale, but instead
|
||||
* requires to instantiate a collator object and then query the case first
|
||||
* attribute (UCOL_CASE_FIRST).
|
||||
* To avoid instantiating an additional collator object whenever we need
|
||||
* to retrieve the default case first value of a specific locale, we
|
||||
* compute the default case first value for every supported locale only
|
||||
* once and then keep a list of all locales which don't use the default
|
||||
* case first setting.
|
||||
* There is almost no difference between lower-case first and when case
|
||||
* first is disabled (UCOL_LOWER_FIRST resp. UCOL_OFF), so we only need to
|
||||
* track locales which use upper-case first as their default setting.
|
||||
*/
|
||||
|
||||
LocaleSet upperCaseFirstLocales;
|
||||
|
||||
bool upperCaseFirstInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available locales which use upper-case first sorting.
|
||||
*/
|
||||
bool ensureUpperCaseFirstLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets |isUpperFirst| to true if |locale| sorts upper-case characters
|
||||
* before lower-case characters.
|
||||
*/
|
||||
bool isUpperCaseFirst(JSContext* cx, JS::HandleString locale, bool* isUpperFirst);
|
||||
|
||||
public:
|
||||
void destroyInstance();
|
||||
|
||||
void trace(JSTracer* trc);
|
||||
|
||||
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
||||
};
|
||||
|
||||
} // namespace intl
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_intl_SharedIntlData_h */
|
||||
+2
-15
@@ -13,7 +13,6 @@
|
||||
#include "jsgc.h"
|
||||
|
||||
#include "gc/Heap.h"
|
||||
#include "gc/IdleGC.h"
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Statistics.h"
|
||||
#include "gc/StoreBuffer.h"
|
||||
@@ -651,13 +650,6 @@ class GCRuntime
|
||||
void onOutOfMallocMemory();
|
||||
void onOutOfMallocMemory(const AutoLockGC& lock);
|
||||
|
||||
/* Idle-time GC notifications. */
|
||||
void notifyJSExecutionStart();
|
||||
void notifyJSExecutionEnd();
|
||||
IdleGCManager& idleGCMgr() {
|
||||
return idleGC;
|
||||
}
|
||||
|
||||
size_t maxMallocBytesAllocated() { return maxMallocBytes; }
|
||||
|
||||
uint64_t nextCellUniqueId() {
|
||||
@@ -974,8 +966,6 @@ class GCRuntime
|
||||
void sweepZones(FreeOp* fop, bool lastGC);
|
||||
void decommitAllWithoutUnlocking(const AutoLockGC& lock);
|
||||
void startDecommit();
|
||||
bool sweepBackgroundFinalizePhaseInParallel(ZoneList& zones, const FinalizePhase& phase,
|
||||
Arena** emptyArenas);
|
||||
void queueZonesForBackgroundSweep(ZoneList& zones);
|
||||
void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks);
|
||||
void assertBackgroundSweepingFinished();
|
||||
@@ -987,9 +977,9 @@ class GCRuntime
|
||||
void sweepTypesAfterCompacting(Zone* zone);
|
||||
void sweepZoneAfterCompacting(Zone* zone);
|
||||
[[nodiscard]] bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
|
||||
Arena*& relocatedListOut, SliceBudget& sliceBudget);
|
||||
Arena*& relocatedListOut, SliceBudget& sliceBudget);
|
||||
void updateTypeDescrObjects(MovingTracer* trc, Zone* zone);
|
||||
void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds);
|
||||
void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount);
|
||||
void updateAllCellPointers(MovingTracer* trc, Zone* zone);
|
||||
void updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock);
|
||||
void protectAndHoldArenas(Arena* arenaList);
|
||||
@@ -1029,9 +1019,6 @@ class GCRuntime
|
||||
GCSchedulingTunables tunables;
|
||||
GCSchedulingState schedulingState;
|
||||
|
||||
/* Idle-time garbage collection manager. */
|
||||
IdleGCManager idleGC;
|
||||
|
||||
MemProfiler mMemProfiler;
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* IDLE-TIME GARBAGE COLLECTION SYSTEM
|
||||
*
|
||||
* Overview
|
||||
* --------
|
||||
* This system implements idle-time-only garbage collection, deferring GC work
|
||||
* to periods when the JavaScript engine is not actively processing code.
|
||||
* This improves perceived responsiveness by avoiding GC pauses during critical
|
||||
* execution windows.
|
||||
*
|
||||
* Architecture
|
||||
* -----------
|
||||
*
|
||||
* The system consists of several key components:
|
||||
*
|
||||
* 1. IdleGCManager (gc/IdleGC.h, gc/IdleGC.cpp)
|
||||
* - Tracks when the JS engine is executing vs. idle
|
||||
* - Maintains timestamp of last execution activity
|
||||
* - Checks if sufficient idle time has passed
|
||||
* - Configurable idle threshold (default: 100ms)
|
||||
* - Can determine which GC reasons should bypass idle checks
|
||||
*
|
||||
* 2. GCRuntime Integration (gc/GCRuntime.h)
|
||||
* - Contains an IdleGCManager instance
|
||||
* - Provides public methods: notifyJSExecutionStart/End()
|
||||
* - Modified checkIfGCAllowedInCurrentState() to check idle status
|
||||
*
|
||||
* 3. Activity Tracking (vm/Runtime.cpp)
|
||||
* - triggerActivityCallback() now notifies IdleGCManager
|
||||
* - Called when JS enters/exits request (execution boundary)
|
||||
* - Updated in jsapi.cpp's StartRequest/StopRequest functions
|
||||
*
|
||||
* 4. Public API (jsapi.h, jsapi.cpp)
|
||||
* - JS_SetIdleGCEnabled() / JS_IsIdleGCEnabled()
|
||||
* - JS_SetIdleGCThreshold() / JS_GetIdleGCThreshold()
|
||||
* - JS_GetIdleTimeSinceLastExecution()
|
||||
* - Allows embedders to configure idle GC behavior
|
||||
*
|
||||
* Behavior
|
||||
* --------
|
||||
*
|
||||
* Normal GC Trigger (Idle GC Enabled):
|
||||
*
|
||||
* 1. JS code executes → notifyJSExecutionStart() called
|
||||
* 2. JS code finishes → notifyJSExecutionEnd() called, timestamp recorded
|
||||
* 3. GC needed → checkIfGCAllowedInCurrentState() checks idle status
|
||||
* 4a. If idle >= threshold → GC proceeds normally
|
||||
* 4b. If still executing/idle < threshold → GC is deferred
|
||||
* 5. Once idle threshold is met, next GC request proceeds
|
||||
*
|
||||
* Critical GC Triggers (Always Proceed):
|
||||
*
|
||||
* The following GC reasons bypass idle checking:
|
||||
* - OUT_OF_MEMORY: Memory pressure conditions
|
||||
* - ALLOC_TRIGGER: Allocation threshold exceeded
|
||||
* - MALLOC_PRESSURE: Malloc pressure from OS
|
||||
* - EAGER_ALLOC_TRIGGER: Eager allocation trigger
|
||||
* - API: Explicit JS API calls
|
||||
* - DETERMINISTIC: Deterministic tests
|
||||
* - EVICT_NURSERY: Nursery eviction
|
||||
* - SHUTDOWN_CC, DESTROY_RUNTIME, LAST_DITCH: Shutdown GCs
|
||||
* - DESTROY_ZONE, COMPARTMENT_REVOKED: Zone/compartment destruction
|
||||
*
|
||||
* Configuration
|
||||
* -------------
|
||||
*
|
||||
* Idle GC Enabled (default: true):
|
||||
* - Enables idle-time-only GC mode
|
||||
* - Can be toggled dynamically via JS_SetIdleGCEnabled()
|
||||
*
|
||||
* Idle Threshold (default: 100ms):
|
||||
* - Minimum idle time before GC is permitted
|
||||
* - Configurable via JS_SetIdleGCThreshold(ms)
|
||||
* - Typical values: 50-200ms depending on application
|
||||
*
|
||||
* Integration Examples
|
||||
* --------------------
|
||||
*
|
||||
* Browser Integration:
|
||||
*
|
||||
* // When browser loads configuration
|
||||
* JS_SetIdleGCEnabled(cx, true);
|
||||
* JS_SetIdleGCThreshold(cx, 100); // 100ms idle threshold
|
||||
*
|
||||
* // Monitor idle GC effectiveness (optional)
|
||||
* uint64_t idleTime = JS_GetIdleTimeSinceLastExecution(cx);
|
||||
* if (idleTime > JS_GetIdleGCThreshold(cx)) {
|
||||
* // System is idle, GC would be allowed if triggered
|
||||
* }
|
||||
*
|
||||
* Disabling for Specific Scenarios:
|
||||
*
|
||||
* // During initialization when nothing is "idle" yet
|
||||
* JS_SetIdleGCEnabled(cx, false);
|
||||
* // ... do initial setup ...
|
||||
* JS_SetIdleGCEnabled(cx, true); // Re-enable for normal operation
|
||||
*
|
||||
* Performance Considerations
|
||||
* --------------------------
|
||||
*
|
||||
* Benefits:
|
||||
* - Reduced jank during active JS execution
|
||||
* - GC pauses moved to idle periods where users won't notice
|
||||
* - Especially effective for interactive applications
|
||||
* - Improves Time-to-Interactive and First Input Delay metrics
|
||||
*
|
||||
* Tradeoffs:
|
||||
* - May accumulate more garbage before collection
|
||||
* - Requires predictable idle periods (not suitable for all workloads)
|
||||
* - Critical memory pressure GCs still proceed immediately
|
||||
*
|
||||
* Tuning:
|
||||
* - Lower threshold (50ms) = more frequent GC, less memory overhead
|
||||
* - Higher threshold (200ms) = less GC overhead, more memory usage
|
||||
* - Optimal value depends on application's execution pattern
|
||||
*
|
||||
* Testing
|
||||
* -------
|
||||
*
|
||||
* Unit Tests:
|
||||
* // Test idle detection
|
||||
* JS_SetIdleGCThreshold(cx, 100);
|
||||
* // Simulate JS execution
|
||||
* cx->runtime()->gc.notifyJSExecutionStart();
|
||||
* // ... wait 50ms ...
|
||||
* MOZ_ASSERT(!cx->runtime()->gc.idleGCMgr().isIdleEnough());
|
||||
* cx->runtime()->gc.notifyJSExecutionEnd();
|
||||
* // ... wait 150ms ...
|
||||
* MOZ_ASSERT(cx->runtime()->gc.idleGCMgr().isIdleEnough());
|
||||
*
|
||||
* Integration Tests:
|
||||
* - Verify GC is deferred during active execution
|
||||
* - Verify GC proceeds after idle period
|
||||
* - Verify critical GC reasons bypass idle check
|
||||
* - Measure latency improvements
|
||||
*
|
||||
* Implementation Notes
|
||||
* --------------------
|
||||
*
|
||||
* Thread Safety:
|
||||
* - IdleGCManager uses mozilla::Atomic for thread-safe state
|
||||
* - TimeStamp operations are atomic
|
||||
* - No additional locking needed beyond existing GC locks
|
||||
*
|
||||
* Compatibility:
|
||||
* - Works with both incremental and non-incremental GC
|
||||
* - Compatible with generational GC
|
||||
* - Works with zone GC and full GC
|
||||
* - Respects existing GC suppression mechanisms
|
||||
*
|
||||
* Future Enhancements
|
||||
* -------------------
|
||||
*
|
||||
* Potential improvements:
|
||||
* - Adaptive idle threshold based on historical GC times
|
||||
* - Per-zone idle configuration
|
||||
* - Integration with browser rendering idle callback API
|
||||
* - Metrics/telemetry for idle GC effectiveness
|
||||
* - Machine learning-based prediction of idle periods
|
||||
* - Cooperative GC scheduling with other subsystems
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gc/IdleGC.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
|
||||
IdleGCManager::IdleGCManager()
|
||||
: lastExecutionTime_(mozilla::TimeStamp::Now()),
|
||||
idleGCEnabled_(true),
|
||||
idleThresholdMs_(100), // 100ms default idle threshold
|
||||
isExecuting_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::notifyJSExecutionStart()
|
||||
{
|
||||
isExecuting_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::notifyJSExecutionEnd()
|
||||
{
|
||||
isExecuting_ = false;
|
||||
lastExecutionTime_ = mozilla::TimeStamp::Now();
|
||||
}
|
||||
|
||||
bool
|
||||
IdleGCManager::isIdleEnough() const
|
||||
{
|
||||
if (!idleGCEnabled_) {
|
||||
return true; // If disabled, always consider idle
|
||||
}
|
||||
|
||||
if (isExecuting_) {
|
||||
return false; // Still executing, not idle
|
||||
}
|
||||
|
||||
uint64_t idleTime = idleTimeSinceLastExecution();
|
||||
return idleTime >= idleThresholdMs_;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IdleGCManager::idleTimeSinceLastExecution() const
|
||||
{
|
||||
mozilla::TimeStamp now = mozilla::TimeStamp::Now();
|
||||
mozilla::TimeDuration idle = now - lastExecutionTime_;
|
||||
return idle.ToMilliseconds();
|
||||
}
|
||||
|
||||
bool
|
||||
IdleGCManager::shouldBypassIdleCheck(JS::gcreason::Reason reason)
|
||||
{
|
||||
// These reasons indicate urgent GC needs that should bypass idle checking
|
||||
switch (reason) {
|
||||
// Allocation and memory pressure conditions.
|
||||
case JS::gcreason::ALLOC_TRIGGER:
|
||||
case JS::gcreason::EAGER_ALLOC_TRIGGER:
|
||||
case JS::gcreason::TOO_MUCH_MALLOC:
|
||||
case JS::gcreason::MEM_PRESSURE:
|
||||
case JS::gcreason::LAST_DITCH:
|
||||
|
||||
// Nursery/store-buffer pressure.
|
||||
case JS::gcreason::OUT_OF_NURSERY:
|
||||
case JS::gcreason::EVICT_NURSERY:
|
||||
case JS::gcreason::FULL_STORE_BUFFER:
|
||||
case JS::gcreason::SHARED_MEMORY_LIMIT:
|
||||
|
||||
// Explicit API calls
|
||||
case JS::gcreason::API:
|
||||
case JS::gcreason::ABORT_GC:
|
||||
|
||||
// Shutdown and finalization
|
||||
case JS::gcreason::SHUTDOWN_CC:
|
||||
case JS::gcreason::DESTROY_RUNTIME:
|
||||
case JS::gcreason::NSJSCONTEXT_DESTROY:
|
||||
case JS::gcreason::XPCONNECT_SHUTDOWN:
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::reset()
|
||||
{
|
||||
lastExecutionTime_ = mozilla::TimeStamp::Now();
|
||||
isExecuting_ = false;
|
||||
}
|
||||
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
@@ -1,118 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef gc_IdleGC_h
|
||||
#define gc_IdleGC_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "js/GCAPI.h"
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
|
||||
/*
|
||||
* Idle-Time Garbage Collection System
|
||||
* ====================================
|
||||
*
|
||||
* This system defers garbage collection to occur only during periods when
|
||||
* the browser is not actively processing JavaScript. This helps maintain
|
||||
* responsiveness by avoiding GC pauses during critical execution windows.
|
||||
*
|
||||
* When JavaScript execution is active, GC triggers are deferred. Once the
|
||||
* JS engine has been idle for a configurable threshold period, pending GC
|
||||
* work is performed immediately or incrementally as appropriate.
|
||||
*
|
||||
* Key characteristics:
|
||||
* - Tracks JavaScript activity via hooks in the execution engine
|
||||
* - Configurable idle time threshold (default: 100ms)
|
||||
* - Can be disabled per-zone or globally
|
||||
* - Works with both incremental and non-incremental GC modes
|
||||
* - Respects critical GC reasons that override idle checking
|
||||
*/
|
||||
|
||||
class IdleGCManager
|
||||
{
|
||||
public:
|
||||
// Initialize the idle GC manager
|
||||
IdleGCManager();
|
||||
|
||||
/*
|
||||
* Called when JavaScript execution begins. Marks the engine as active.
|
||||
*/
|
||||
void notifyJSExecutionStart();
|
||||
|
||||
/*
|
||||
* Called when JavaScript execution ends. Records the end time for
|
||||
* idle detection purposes.
|
||||
*/
|
||||
void notifyJSExecutionEnd();
|
||||
|
||||
/*
|
||||
* Check if the system has been idle for long enough to permit GC.
|
||||
* Returns true if sufficient idle time has passed since last JS execution.
|
||||
*/
|
||||
bool isIdleEnough() const;
|
||||
|
||||
/*
|
||||
* Get the amount of idle time since the last JS execution.
|
||||
* Returns time in milliseconds.
|
||||
*/
|
||||
uint64_t idleTimeSinceLastExecution() const;
|
||||
|
||||
/*
|
||||
* Set the idle threshold - minimum idle time before GC is permitted.
|
||||
* Time is in milliseconds. Default is 100ms.
|
||||
*/
|
||||
void setIdleThresholdMs(uint64_t thresholdMs) {
|
||||
idleThresholdMs_ = thresholdMs;
|
||||
}
|
||||
|
||||
uint64_t idleThresholdMs() const {
|
||||
return idleThresholdMs_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable or disable idle-time-only GC mode.
|
||||
*/
|
||||
void setIdleGCEnabled(bool enabled) {
|
||||
idleGCEnabled_ = enabled;
|
||||
}
|
||||
|
||||
bool isIdleGCEnabled() const {
|
||||
return idleGCEnabled_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a GC reason should bypass idle checking.
|
||||
* Critical reasons (OOM-like pressure, nursery pressure, explicit requests)
|
||||
* always proceed.
|
||||
*/
|
||||
static bool shouldBypassIdleCheck(JS::gcreason::Reason reason);
|
||||
|
||||
/*
|
||||
* Reset idle tracking state (used during GC or at shutdown).
|
||||
*/
|
||||
void reset();
|
||||
|
||||
private:
|
||||
// Timestamp of the last JavaScript execution activity
|
||||
mozilla::TimeStamp lastExecutionTime_;
|
||||
|
||||
// Whether idle-time-only GC mode is enabled
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> idleGCEnabled_;
|
||||
|
||||
// Minimum idle time (in milliseconds) before GC is permitted
|
||||
mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> idleThresholdMs_;
|
||||
|
||||
// Whether the JS engine is currently executing
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> isExecuting_;
|
||||
};
|
||||
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
|
||||
#endif // gc_IdleGC_h
|
||||
@@ -28,6 +28,7 @@
|
||||
\
|
||||
_(IntlIsCollator) \
|
||||
_(IntlIsDateTimeFormat) \
|
||||
_(IntlIsListFormat) \
|
||||
_(IntlIsNumberFormat) \
|
||||
_(IntlIsPluralRules) \
|
||||
_(IntlIsRelativeTimeFormat) \
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "builtin/TypedObject.h"
|
||||
#include "builtin/intl/Collator.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
#include "builtin/intl/PluralRules.h"
|
||||
#include "builtin/intl/RelativeTimeFormat.h"
|
||||
@@ -113,6 +114,8 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
|
||||
return inlineHasClass(callInfo, &CollatorObject::class_);
|
||||
case InlinableNative::IntlIsDateTimeFormat:
|
||||
return inlineHasClass(callInfo, &DateTimeFormatObject::class_);
|
||||
case InlinableNative::IntlIsListFormat:
|
||||
return inlineHasClass(callInfo, &ListFormatObject::class_);
|
||||
case InlinableNative::IntlIsNumberFormat:
|
||||
return inlineHasClass(callInfo, &NumberFormatObject::class_);
|
||||
case InlinableNative::IntlIsPluralRules:
|
||||
|
||||
@@ -1478,36 +1478,6 @@ JS_SetGCParametersBasedOnAvailableMemory(JSContext* cx, uint32_t availMem)
|
||||
JS_SetGCParameter(cx, config[i].key, config[i].value);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCEnabled(JSContext* cx, bool enabled)
|
||||
{
|
||||
cx->gc.idleGCMgr().setIdleGCEnabled(enabled);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
JS_IsIdleGCEnabled(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().isIdleGCEnabled();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCThreshold(JSContext* cx, uint64_t milliseconds)
|
||||
{
|
||||
cx->gc.idleGCMgr().setIdleThresholdMs(milliseconds);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleGCThreshold(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().idleThresholdMs();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleTimeSinceLastExecution(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().idleTimeSinceLastExecution();
|
||||
}
|
||||
|
||||
|
||||
JS_PUBLIC_API(JSString*)
|
||||
JS_NewExternalString(JSContext* cx, const char16_t* chars, size_t length,
|
||||
|
||||
+7
-41
@@ -1719,40 +1719,6 @@ JS_GetGCParameter(JSContext* cx, JSGCParamKey key);
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetGCParametersBasedOnAvailableMemory(JSContext* cx, uint32_t availMem);
|
||||
|
||||
/*
|
||||
* Idle-time garbage collection control.
|
||||
* These functions allow control over when GC occurs - specifically, whether
|
||||
* GC should only run when the browser/application is not doing active JS work.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable or disable idle-time-only GC mode.
|
||||
* When enabled, GC is deferred until the JS engine has been idle for the
|
||||
* configured threshold period (default: 100ms).
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCEnabled(JSContext* cx, bool enabled);
|
||||
|
||||
extern JS_PUBLIC_API(bool)
|
||||
JS_IsIdleGCEnabled(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Set the minimum idle time (in milliseconds) before GC is permitted.
|
||||
* Use this to configure how long the JS engine must be inactive before
|
||||
* pending GC work can proceed.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCThreshold(JSContext* cx, uint64_t milliseconds);
|
||||
|
||||
extern JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleGCThreshold(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Get the current idle time since the last JS execution.
|
||||
*/
|
||||
extern JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleTimeSinceLastExecution(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Create a new JSString whose chars member refers to external memory, i.e.,
|
||||
* memory requiring application-specific finalization.
|
||||
@@ -4904,15 +4870,15 @@ class JS_PUBLIC_API(JSErrorNotes)
|
||||
js::UniquePtr<JSErrorNotes> copy(JSContext* cx);
|
||||
|
||||
class iterator
|
||||
{
|
||||
{
|
||||
private:
|
||||
js::UniquePtr<Note>* note_;
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = js::UniquePtr<Note>;
|
||||
using difference_type = ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = js::UniquePtr<Note>;
|
||||
using difference_type = ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
|
||||
explicit iterator(js::UniquePtr<Note>* note = nullptr) : note_(note)
|
||||
{}
|
||||
|
||||
@@ -2877,6 +2877,11 @@ ToWindowIfWindowProxy(JSObject* obj);
|
||||
extern bool
|
||||
AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
|
||||
|
||||
// Create and add the Intl.ListFormat constructor function to the provided
|
||||
// object.
|
||||
extern bool
|
||||
AddListFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
class NativeProfiler
|
||||
|
||||
+56
-272
@@ -214,7 +214,6 @@
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCTrace.h"
|
||||
#include "gc/IdleGC.h"
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/Memory.h"
|
||||
#include "gc/Policy.h"
|
||||
@@ -1571,7 +1570,7 @@ ArenaLists::prepareForIncrementalGC()
|
||||
{
|
||||
purge();
|
||||
for (auto i : AllAllocKinds()) {
|
||||
arenaLists[i].moveCursorToEnd();
|
||||
arenaLists[i].moveCursorToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2118,7 +2117,7 @@ ArenasToUpdate::next(AutoLockHelperThreadState& lock)
|
||||
// Find the next arena to update.
|
||||
//
|
||||
// This iterates through the GC thing kinds filtered by shouldProcessKind(),
|
||||
// and then through the arenas of that kind. All state is held in the
|
||||
// and then through thea arenas of that kind. All state is held in the
|
||||
// object and we just return when we find an arena.
|
||||
|
||||
for (; kind < AllocKind::LIMIT; kind = nextAllocKind(kind)) {
|
||||
@@ -2208,12 +2207,21 @@ UpdatePointersTask::run()
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
|
||||
static const size_t MinCellUpdateBackgroundTasks = 1;
|
||||
static const size_t MinCellUpdateBackgroundTasks = 2;
|
||||
static const size_t MaxCellUpdateBackgroundTasks = 8;
|
||||
|
||||
static bool
|
||||
CanUpdateKindInBackground(AllocKind kind)
|
||||
static size_t
|
||||
CellUpdateBackgroundTaskCount()
|
||||
{
|
||||
if (!CanUseExtraThreads())
|
||||
return 0;
|
||||
|
||||
size_t targetTaskCount = HelperThreadState().cpuCount / 2;
|
||||
return Min(Max(targetTaskCount, MinCellUpdateBackgroundTasks), MaxCellUpdateBackgroundTasks);
|
||||
}
|
||||
|
||||
static bool
|
||||
CanUpdateKindInBackground(AllocKind kind) {
|
||||
// We try to update as many GC things in parallel as we can, but there are
|
||||
// kinds for which this might not be safe:
|
||||
// - we assume JSObjects that are foreground finalized are not safe to
|
||||
@@ -2225,34 +2233,6 @@ CanUpdateKindInBackground(AllocKind kind)
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
CountBackgroundUpdateArenas(Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
size_t arenaCount = 0;
|
||||
for (AllocKind kind : kinds) {
|
||||
MOZ_ASSERT(CanUpdateKindInBackground(kind));
|
||||
for (Arena* arena = zone->arenas.getFirstArena(kind); arena; arena = arena->next)
|
||||
arenaCount++;
|
||||
}
|
||||
return arenaCount;
|
||||
}
|
||||
|
||||
static size_t
|
||||
CellUpdateBackgroundTaskCount(Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
if (!CanUseExtraThreads() || kinds.isEmpty())
|
||||
return 0;
|
||||
|
||||
size_t arenaCount = CountBackgroundUpdateArenas(zone, kinds);
|
||||
if (arenaCount < UpdatePointersTask::MaxArenasToProcess * 2)
|
||||
return 0;
|
||||
|
||||
size_t targetTaskCount = HelperThreadState().cpuCount / 2;
|
||||
size_t workTaskCount = arenaCount / UpdatePointersTask::MaxArenasToProcess;
|
||||
targetTaskCount = Min(targetTaskCount, workTaskCount);
|
||||
return Min(Max(targetTaskCount, MinCellUpdateBackgroundTasks), MaxCellUpdateBackgroundTasks);
|
||||
}
|
||||
|
||||
static AllocKinds
|
||||
ForegroundUpdateKinds(AllocKinds kinds)
|
||||
{
|
||||
@@ -2273,15 +2253,10 @@ GCRuntime::updateTypeDescrObjects(MovingTracer* trc, Zone* zone)
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds)
|
||||
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount)
|
||||
{
|
||||
MOZ_ASSERT(trc);
|
||||
|
||||
AllocKinds fgKinds = ForegroundUpdateKinds(kinds);
|
||||
AllocKinds fgKinds = bgTaskCount == 0 ? kinds : ForegroundUpdateKinds(kinds);
|
||||
AllocKinds bgKinds = kinds - fgKinds;
|
||||
size_t bgTaskCount = CellUpdateBackgroundTaskCount(zone, bgKinds);
|
||||
if (bgTaskCount == 0)
|
||||
fgKinds = kinds;
|
||||
|
||||
ArenasToUpdate fgArenas(zone, fgKinds);
|
||||
ArenasToUpdate bgArenas(zone, bgKinds);
|
||||
@@ -2376,13 +2351,15 @@ GCRuntime::updateAllCellPointers(MovingTracer* trc, Zone* zone)
|
||||
{
|
||||
AutoDisableProxyCheck noProxyCheck(rt); // These checks assert when run in parallel.
|
||||
|
||||
updateCellPointers(trc, zone, UpdatePhaseMisc);
|
||||
size_t bgTaskCount = CellUpdateBackgroundTaskCount();
|
||||
|
||||
updateCellPointers(trc, zone, UpdatePhaseMisc, bgTaskCount);
|
||||
|
||||
// Update TypeDescrs before all other objects as typed objects access these
|
||||
// objects when we trace them.
|
||||
updateTypeDescrObjects(trc, zone);
|
||||
|
||||
updateCellPointers(trc, zone, UpdatePhaseObjects);
|
||||
updateCellPointers(trc, zone, UpdatePhaseObjects, bgTaskCount);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2688,17 +2665,6 @@ ArenaLists::backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty)
|
||||
lists->backgroundFinalizeState[thingKind] = BFS_DONE;
|
||||
}
|
||||
|
||||
void
|
||||
ArenaLists::backgroundFinalizePhase(FreeOp* fop, const FinalizePhase& phase, Arena** empty)
|
||||
{
|
||||
for (auto kind : phase.kinds) {
|
||||
Arena* arenas = arenaListsToSweep[kind];
|
||||
MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
|
||||
if (arenas)
|
||||
backgroundFinalize(fop, arenas, empty);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArenaLists::queueForegroundObjectsForSweep(FreeOp* fop)
|
||||
{
|
||||
@@ -3037,138 +3003,6 @@ js::gc::BackgroundDecommitTask::run()
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundFinalizeTask : public GCParallelTaskHelper<BackgroundFinalizeTask>
|
||||
{
|
||||
Zone* zone_;
|
||||
const FinalizePhase* phase_;
|
||||
Arena* emptyArenas_;
|
||||
|
||||
BackgroundFinalizeTask(const BackgroundFinalizeTask&) = delete;
|
||||
|
||||
public:
|
||||
BackgroundFinalizeTask(Zone* zone, const FinalizePhase* phase)
|
||||
: zone_(zone),
|
||||
phase_(phase),
|
||||
emptyArenas_(nullptr)
|
||||
{}
|
||||
|
||||
BackgroundFinalizeTask(BackgroundFinalizeTask&& other)
|
||||
: GCParallelTaskHelper(mozilla::Move(other)),
|
||||
zone_(other.zone_),
|
||||
phase_(other.phase_),
|
||||
emptyArenas_(other.emptyArenas_)
|
||||
{
|
||||
other.emptyArenas_ = nullptr;
|
||||
}
|
||||
|
||||
void run() {
|
||||
AutoSetThreadIsSweeping threadIsSweeping;
|
||||
finalize();
|
||||
}
|
||||
|
||||
void runAlreadySweeping() {
|
||||
#ifdef DEBUG
|
||||
MOZ_ASSERT(CurrentThreadIsGCSweeping());
|
||||
#endif
|
||||
finalize();
|
||||
}
|
||||
|
||||
private:
|
||||
void finalize() {
|
||||
FreeOp fop(nullptr);
|
||||
zone_->arenas.backgroundFinalizePhase(&fop, *phase_, &emptyArenas_);
|
||||
}
|
||||
|
||||
public:
|
||||
Arena* takeEmptyArenas() {
|
||||
Arena* empty = emptyArenas_;
|
||||
emptyArenas_ = nullptr;
|
||||
return empty;
|
||||
}
|
||||
};
|
||||
|
||||
using BackgroundFinalizeTaskVector =
|
||||
Vector<BackgroundFinalizeTask, 0, SystemAllocPolicy>;
|
||||
|
||||
static size_t
|
||||
IdleHelperThreadCount(const AutoLockHelperThreadState&)
|
||||
{
|
||||
if (!HelperThreadState().threads)
|
||||
return 0;
|
||||
|
||||
size_t idle = 0;
|
||||
for (const auto& thread : *HelperThreadState().threads) {
|
||||
if (thread.idle())
|
||||
idle++;
|
||||
}
|
||||
return idle;
|
||||
}
|
||||
|
||||
static void
|
||||
PrependArenaList(Arena** head, Arena* arenas)
|
||||
{
|
||||
if (!arenas)
|
||||
return;
|
||||
|
||||
Arena* tail = arenas;
|
||||
while (tail->next)
|
||||
tail = tail->next;
|
||||
tail->next = *head;
|
||||
*head = arenas;
|
||||
}
|
||||
|
||||
bool
|
||||
GCRuntime::sweepBackgroundFinalizePhaseInParallel(ZoneList& zones, const FinalizePhase& phase,
|
||||
Arena** emptyArenas)
|
||||
{
|
||||
if (!CanUseExtraThreads())
|
||||
return false;
|
||||
|
||||
size_t zoneCount = 0;
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
zoneCount++;
|
||||
|
||||
if (zoneCount < 2)
|
||||
return false;
|
||||
|
||||
BackgroundFinalizeTaskVector tasks;
|
||||
if (!tasks.reserve(zoneCount))
|
||||
return false;
|
||||
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
tasks.infallibleEmplaceBack(zone, &phase);
|
||||
|
||||
size_t tasksStarted = 0;
|
||||
|
||||
{
|
||||
AutoLockHelperThreadState helperLock;
|
||||
|
||||
// sweepBackgroundThings() itself runs as a GC helper task. Do not queue
|
||||
// nested GC parallel tasks unless at least one other helper is idle.
|
||||
if (IdleHelperThreadCount(helperLock) == 0)
|
||||
return false;
|
||||
|
||||
for (; tasksStarted < tasks.length(); tasksStarted++) {
|
||||
if (!tasks[tasksStarted].startWithLockHeld(helperLock))
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
AutoUnlockHelperThreadState unlock(helperLock);
|
||||
for (size_t i = tasksStarted; i < tasks.length(); i++)
|
||||
tasks[i].runAlreadySweeping();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tasksStarted; i++)
|
||||
tasks[i].joinWithLockHeld(helperLock);
|
||||
}
|
||||
|
||||
for (auto& task : tasks)
|
||||
PrependArenaList(emptyArenas, task.takeEmptyArenas());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks)
|
||||
{
|
||||
@@ -3181,12 +3015,14 @@ GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks)
|
||||
Arena* emptyArenas = nullptr;
|
||||
FreeOp fop(nullptr);
|
||||
for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) {
|
||||
const FinalizePhase& finalizePhase = BackgroundFinalizePhases[phase];
|
||||
if (sweepBackgroundFinalizePhaseInParallel(zones, finalizePhase, &emptyArenas))
|
||||
continue;
|
||||
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
zone->arenas.backgroundFinalizePhase(&fop, finalizePhase, &emptyArenas);
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone()) {
|
||||
for (auto kind : BackgroundFinalizePhases[phase].kinds) {
|
||||
Arena* arenas = zone->arenas.arenaListsToSweep[kind];
|
||||
MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
|
||||
if (arenas)
|
||||
ArenaLists::backgroundFinalize(&fop, arenas, &emptyArenas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AutoLockGC lock(rt);
|
||||
@@ -3745,14 +3581,14 @@ ShouldCollectZone(Zone* zone, JS::gcreason::Reason reason)
|
||||
// Normally we collect all scheduled zones.
|
||||
if (reason != JS::gcreason::COMPARTMENT_REVIVED)
|
||||
return zone->isGCScheduled();
|
||||
|
||||
|
||||
// If we are repeating a GC because we noticed dead compartments haven't
|
||||
// been collected, then only collect zones containing those compartments.
|
||||
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
|
||||
if (comp->scheduledForDestruction)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3818,7 +3654,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
* on. If the value of keepAtoms() changes between GC slices, then we'll
|
||||
* cancel the incremental GC. See IsIncrementalGCSafe.
|
||||
*/
|
||||
|
||||
|
||||
if (isFull && !rt->keepAtoms()) {
|
||||
Zone* atomsZone = rt->atomsCompartment(lock)->zone();
|
||||
if (atomsZone->isGCScheduled()) {
|
||||
@@ -3834,7 +3670,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
|
||||
/*
|
||||
* Ensure that after the start of a collection we don't allocate into any
|
||||
* existing arenas, as this can cause unreachable things to be marked.
|
||||
* existing arenas, as this can cause unreachable things to be marked.
|
||||
*/
|
||||
if (isIncremental) {
|
||||
for (GCZonesIter zone(rt); !zone.done(); zone.next())
|
||||
@@ -3926,7 +3762,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
bufferGrayRoots();
|
||||
markCompartments();
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3965,9 +3801,9 @@ GCRuntime::markCompartments()
|
||||
*/
|
||||
|
||||
/* Propagate the maybeAlive flag via cross-compartment edges. */
|
||||
|
||||
|
||||
Vector<JSCompartment*, 0, js::SystemAllocPolicy> workList;
|
||||
|
||||
|
||||
for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) {
|
||||
if (comp->maybeAlive) {
|
||||
if (!workList.append(comp))
|
||||
@@ -3988,9 +3824,9 @@ GCRuntime::markCompartments()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Set scheduleForDestruction based on maybeAlive. */
|
||||
|
||||
|
||||
for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) {
|
||||
MOZ_ASSERT(!comp->scheduledForDestruction);
|
||||
if (!comp->maybeAlive && !rt->isAtomsCompartment(comp))
|
||||
@@ -4593,9 +4429,7 @@ MAKE_GC_SWEEP_TASK(SweepBaseShapesTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepInitialShapesTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepObjectGroupsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepRegExpsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepSavedStacksTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepSelfHostingScriptSourceTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepNativeIteratorsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepMiscTask);
|
||||
#undef MAKE_GC_SWEEP_TASK
|
||||
|
||||
/* virtual */ void
|
||||
@@ -4628,25 +4462,13 @@ SweepRegExpsTask::run()
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepSavedStacksTask::run()
|
||||
SweepMiscTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) {
|
||||
c->sweepSavedStacks();
|
||||
}
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepSelfHostingScriptSourceTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next())
|
||||
c->sweepSelfHostingScriptSource();
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepNativeIteratorsTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next())
|
||||
c->sweepNativeIterators();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -4687,7 +4509,7 @@ PrepareWeakCacheTasks(JSRuntime* rt)
|
||||
WeakCacheTaskVector out;
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
|
||||
for (JS::WeakCache<void*>* cache : zone->weakCaches_) {
|
||||
if (!out.emplaceBack(rt, *cache)) {
|
||||
if (!out.append(SweepWeakCacheTask(rt, *cache))) {
|
||||
SweepWeakCachesFromMainThread(rt);
|
||||
return WeakCacheTaskVector();
|
||||
}
|
||||
@@ -4729,9 +4551,7 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
SweepCCWrappersTask sweepCCWrappersTask(rt);
|
||||
SweepObjectGroupsTask sweepObjectGroupsTask(rt);
|
||||
SweepRegExpsTask sweepRegExpsTask(rt);
|
||||
SweepSavedStacksTask sweepSavedStacksTask(rt);
|
||||
SweepSelfHostingScriptSourceTask sweepSelfHostingScriptSourceTask(rt);
|
||||
SweepNativeIteratorsTask sweepNativeIteratorsTask(rt);
|
||||
SweepMiscTask sweepMiscTask(rt);
|
||||
WeakCacheTaskVector sweepCacheTasks = PrepareWeakCacheTasks(rt);
|
||||
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
|
||||
@@ -4779,9 +4599,7 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
startTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
|
||||
startTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
startTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
startTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
startTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
@@ -4800,6 +4618,7 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
c->sweepJitCompartment(&fop);
|
||||
c->sweepTemplateObjects();
|
||||
}
|
||||
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next())
|
||||
zone->sweepWeakMaps();
|
||||
|
||||
@@ -4859,9 +4678,7 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
joinTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
|
||||
joinTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
joinTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
joinTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
joinTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
@@ -5188,7 +5005,7 @@ GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess&
|
||||
|
||||
// Reset phase index.
|
||||
sweepPhaseIndex = 0;
|
||||
|
||||
|
||||
endSweepingZoneGroup();
|
||||
getNextZoneGroup();
|
||||
if (!currentZoneGroup)
|
||||
@@ -5290,7 +5107,6 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
|
||||
|
||||
Arena* relocatedArenas = nullptr;
|
||||
|
||||
while (!zonesToMaybeCompact.isEmpty()) {
|
||||
// TODO: JSScripts can move. If the sampler interrupts the GC in the
|
||||
// middle of relocating an arena, invalid JSScript pointers may be
|
||||
@@ -5742,7 +5558,7 @@ gc::AbortReason
|
||||
gc::IsIncrementalGCUnsafe(JSRuntime* rt)
|
||||
{
|
||||
MOZ_ASSERT(!rt->mainThread.suppressGC);
|
||||
|
||||
|
||||
if (rt->keepAtoms())
|
||||
return gc::AbortReason::KeepAtomsSet;
|
||||
|
||||
@@ -5763,7 +5579,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
|
||||
else if (mode != JSGC_MODE_INCREMENTAL)
|
||||
unsafeReason = gc::AbortReason::ModeChange;
|
||||
}
|
||||
|
||||
|
||||
if (unsafeReason != AbortReason::None) {
|
||||
resetIncrementalGC(unsafeReason, lock);
|
||||
budget.makeUnlimited();
|
||||
@@ -5771,7 +5587,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (isTooMuchMalloc()) {
|
||||
budget.makeUnlimited();
|
||||
@@ -6011,12 +5827,6 @@ GCRuntime::checkCanCallAPI()
|
||||
bool
|
||||
GCRuntime::checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason)
|
||||
{
|
||||
auto isTabCloseReason = [](JS::gcreason::Reason r) {
|
||||
return r == JS::gcreason::PAGE_HIDE ||
|
||||
r == JS::gcreason::POST_COMPARTMENT ||
|
||||
r == JS::gcreason::NSJSCONTEXT_DESTROY;
|
||||
};
|
||||
|
||||
if (rt->mainThread.suppressGC)
|
||||
return false;
|
||||
|
||||
@@ -6025,12 +5835,6 @@ GCRuntime::checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason)
|
||||
if (rt->isBeingDestroyed() && !IsShutdownGC(reason))
|
||||
return false;
|
||||
|
||||
// Allow fast tab-close cleanup even when the runtime is otherwise busy.
|
||||
if (!isTabCloseReason(reason) &&
|
||||
!IdleGCManager::shouldBypassIdleCheck(reason) &&
|
||||
!idleGC.isIdleEnough())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6038,15 +5842,15 @@ bool
|
||||
GCRuntime::shouldRepeatForDeadZone(JS::gcreason::Reason reason)
|
||||
{
|
||||
MOZ_ASSERT_IF(reason == JS::gcreason::COMPARTMENT_REVIVED, !isIncremental);
|
||||
|
||||
|
||||
if (!isIncremental || isIncrementalGCInProgress())
|
||||
return false;
|
||||
|
||||
|
||||
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
|
||||
if (c->scheduledForDestruction)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6068,13 +5872,13 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
do {
|
||||
poked = false;
|
||||
bool wasReset = gcCycle(nonincrementalByAPI, budget, reason);
|
||||
|
||||
|
||||
bool repeatForDeadZone = false;
|
||||
if (poked && cleanUpEverything) {
|
||||
/* Need to re-schedule all zones for GC. */
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
|
||||
|
||||
|
||||
} else if (shouldRepeatForDeadZone(reason) && !wasReset) {
|
||||
/*
|
||||
* This code makes an extra effort to collect compartments that we
|
||||
@@ -6084,7 +5888,7 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
repeatForDeadZone = true;
|
||||
reason = JS::gcreason::COMPARTMENT_REVIVED;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* If we reset an existing GC, we need to start a new one. Also, we
|
||||
@@ -6108,16 +5912,8 @@ js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC()
|
||||
SliceBudget
|
||||
GCRuntime::defaultBudget(JS::gcreason::Reason reason, int64_t millis)
|
||||
{
|
||||
auto isTabCloseReason = [](JS::gcreason::Reason r) {
|
||||
return r == JS::gcreason::PAGE_HIDE ||
|
||||
r == JS::gcreason::POST_COMPARTMENT ||
|
||||
r == JS::gcreason::NSJSCONTEXT_DESTROY;
|
||||
};
|
||||
|
||||
if (millis == 0) {
|
||||
if (isTabCloseReason(reason))
|
||||
millis = 3;
|
||||
else if (reason == JS::gcreason::ALLOC_TRIGGER)
|
||||
if (reason == JS::gcreason::ALLOC_TRIGGER)
|
||||
millis = defaultSliceBudget();
|
||||
else if (schedulingState.inHighFrequencyGCMode() && tunables.isDynamicMarkSliceEnabled())
|
||||
millis = defaultSliceBudget() * IGC_MARK_SLICE_MULTIPLIER;
|
||||
@@ -6205,18 +6001,6 @@ GCRuntime::notifyDidPaint()
|
||||
interFrameGC = false;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::notifyJSExecutionStart()
|
||||
{
|
||||
idleGC.notifyJSExecutionStart();
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::notifyJSExecutionEnd()
|
||||
{
|
||||
idleGC.notifyJSExecutionEnd();
|
||||
}
|
||||
|
||||
static bool
|
||||
ZonesSelected(JSRuntime* rt)
|
||||
{
|
||||
|
||||
@@ -804,7 +804,6 @@ class ArenaLists
|
||||
|
||||
bool foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sliceBudget,
|
||||
SortedArenaList& sweepList);
|
||||
void backgroundFinalizePhase(FreeOp* fop, const FinalizePhase& phase, Arena** empty);
|
||||
static void backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty);
|
||||
|
||||
// When finalizing arenas, whether to keep empty arenas on the list or
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
real(Symbol, InitSymbolClass, OCLASP(Symbol)) \
|
||||
IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \
|
||||
IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \
|
||||
IF_INTL(real,imaginary) (ListFormat, InitViaClassSpec, OCLASP(ListFormat)) \
|
||||
IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
|
||||
real(Reflect, InitReflect, nullptr) \
|
||||
real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \
|
||||
|
||||
+2
-1
@@ -126,6 +126,7 @@ main_deunified_sources = [
|
||||
'builtin/intl/IntlObject.cpp',
|
||||
'builtin/intl/LanguageTag.cpp',
|
||||
'builtin/intl/LanguageTagGenerated.cpp',
|
||||
'builtin/intl/ListFormat.cpp',
|
||||
'builtin/intl/Locale.cpp',
|
||||
'builtin/intl/NumberFormat.cpp',
|
||||
'builtin/intl/PluralRules.cpp',
|
||||
@@ -172,7 +173,6 @@ main_deunified_sources = [
|
||||
'gc/Allocator.cpp',
|
||||
'gc/Barrier.cpp',
|
||||
'gc/GCTrace.cpp',
|
||||
'gc/IdleGC.cpp',
|
||||
'gc/Iteration.cpp',
|
||||
'gc/Marking.cpp',
|
||||
'gc/Memory.cpp',
|
||||
@@ -746,6 +746,7 @@ selfhosted.inputs = [
|
||||
'builtin/intl/CommonFunctions.js',
|
||||
'builtin/intl/DateTimeFormat.js',
|
||||
'builtin/intl/IntlObject.js',
|
||||
'builtin/intl/ListFormat.js',
|
||||
'builtin/intl/NumberFormat.js',
|
||||
'builtin/intl/PluralRules.js',
|
||||
'builtin/intl/RelativeTimeFormat.js',
|
||||
|
||||
@@ -935,6 +935,10 @@ AddIntlExtras(JSContext* cx, unsigned argc, Value* vp)
|
||||
if (!js::AddMozDateTimeFormatConstructor(cx, intl))
|
||||
return false;
|
||||
|
||||
if (!js::AddListFormatConstructor(cx, intl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
macro(dotStaticInitializers, dotStaticInitializers, ".staticInitializers") \
|
||||
macro(dotStaticFieldKeys, dotStaticFieldKeys, ".staticFieldKeys") \
|
||||
macro(each, each, "each") \
|
||||
macro(element, element, "element") \
|
||||
macro(elementType, elementType, "elementType") \
|
||||
macro(else, else_, "else") \
|
||||
macro(empty, empty, "") \
|
||||
@@ -204,6 +205,7 @@
|
||||
macro(Infinity, Infinity, "Infinity") \
|
||||
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
|
||||
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
|
||||
macro(InitializeListFormat, InitializeListFormat, "InitializeListFormat") \
|
||||
macro(InitializeLocale, InitializeLocale, "InitializeLocale") \
|
||||
macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \
|
||||
macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "builtin/WeakMapObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "builtin/WeakSetObject.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "vm/Debugger.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
|
||||
+10
-11
@@ -478,8 +478,7 @@ js::CancelOffThreadParses(JSRuntime* rt)
|
||||
}
|
||||
|
||||
// Clean up any parse tasks which haven't been finished by the main thread.
|
||||
GlobalHelperThreadState::ParseTaskVector& finished =
|
||||
HelperThreadState().parseFinishedList(lock);
|
||||
GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList(lock);
|
||||
while (true) {
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < finished.length(); i++) {
|
||||
@@ -487,8 +486,7 @@ js::CancelOffThreadParses(JSRuntime* rt)
|
||||
if (task->runtimeMatches(rt)) {
|
||||
found = true;
|
||||
AutoUnlockHelperThreadState unlock(lock);
|
||||
HelperThreadState().cancelParseTask(rt->contextFromMainThread(),
|
||||
task->kind, task);
|
||||
HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task);
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
@@ -970,7 +968,8 @@ GlobalHelperThreadState::maxGCParallelThreads() const
|
||||
bool
|
||||
GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
if (wasmWorklist(lock).empty())
|
||||
// Don't execute an wasm job if an earlier one failed.
|
||||
if (wasmWorklist(lock).empty() || numWasmFailedJobs)
|
||||
return false;
|
||||
|
||||
// Honor the maximum allowed threads to compile wasm jobs at once,
|
||||
@@ -1420,13 +1419,13 @@ HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
|
||||
success = wasm::CompileFunction(task);
|
||||
}
|
||||
|
||||
// Append the task to the finished queue owned by its module generator.
|
||||
if (!success)
|
||||
task->setFailed();
|
||||
// On success, try to move work to the finished list.
|
||||
if (success)
|
||||
success = HelperThreadState().wasmFinishedList(locked).append(task);
|
||||
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!task->finishedList()->append(task))
|
||||
oomUnsafe.crash("HelperThread::handleWasmWorkload");
|
||||
// On failure, note the failure for harvesting by the parent.
|
||||
if (!success)
|
||||
HelperThreadState().noteWasmFailure(locked);
|
||||
|
||||
// Notify the main thread in case it's waiting.
|
||||
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
|
||||
|
||||
@@ -89,8 +89,8 @@ class GlobalHelperThreadState
|
||||
wasm::IonCompileTaskPtrVector wasmWorklist_, wasmFinishedList_;
|
||||
|
||||
public:
|
||||
// Helper-thread initiated wasm compilations are serialized to avoid the
|
||||
// deadlock scenario described in WasmGenerator.cpp.
|
||||
// For now, only allow a single parallel wasm compilation to happen at a
|
||||
// time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc.
|
||||
mozilla::Atomic<bool> wasmCompilationInProgress;
|
||||
|
||||
private:
|
||||
|
||||
@@ -673,12 +673,6 @@ JSRuntime::traceSharedIntlData(JSTracer* trc)
|
||||
void
|
||||
JSRuntime::triggerActivityCallback(bool active)
|
||||
{
|
||||
if (active) {
|
||||
gc.notifyJSExecutionStart();
|
||||
} else {
|
||||
gc.notifyJSExecutionEnd();
|
||||
}
|
||||
|
||||
if (!activityCallback)
|
||||
return;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "builtin/intl/Collator.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/IntlObject.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "builtin/intl/Locale.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
#include "builtin/intl/PluralRules.h"
|
||||
@@ -2556,6 +2557,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_FN("intl_patternForStyle", intl_patternForStyle, 6, 0),
|
||||
JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
|
||||
JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
|
||||
JS_FN("intl_FormatList", intl_FormatList, 3, 0),
|
||||
JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2,0),
|
||||
JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2,0),
|
||||
JS_FN("intl_ValidateAndCanonicalizeLanguageTag", intl_ValidateAndCanonicalizeLanguageTag, 2, 0),
|
||||
@@ -2569,6 +2571,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_INLINABLE_FN("IsDateTimeFormat",
|
||||
intrinsic_IsInstanceOfBuiltin<DateTimeFormatObject>, 1,0,
|
||||
IntlIsDateTimeFormat),
|
||||
JS_INLINABLE_FN("IsListFormat",
|
||||
intrinsic_IsInstanceOfBuiltin<ListFormatObject>, 1, 0,
|
||||
IntlIsListFormat),
|
||||
JS_INLINABLE_FN("IsNumberFormat",
|
||||
intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
|
||||
IntlIsNumberFormat),
|
||||
|
||||
@@ -54,7 +54,6 @@ ModuleGenerator::ModuleGenerator(ImportVector&& imports)
|
||||
lastPatchedCallsite_(0),
|
||||
startOfUnpatchedCallsites_(0),
|
||||
parallel_(false),
|
||||
parallelCompilationInProgressOnHelperThread_(false),
|
||||
outstanding_(0),
|
||||
activeFuncDef_(nullptr),
|
||||
startedFuncDefs_(false),
|
||||
@@ -72,21 +71,18 @@ ModuleGenerator::~ModuleGenerator()
|
||||
AutoLockHelperThreadState lock;
|
||||
while (true) {
|
||||
IonCompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock);
|
||||
for (size_t i = worklist.length(); i > 0;) {
|
||||
if (worklist[i - 1]->finishedList() == &finishedTasks_) {
|
||||
HelperThreadState().remove(worklist, &i);
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
outstanding_--;
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(outstanding_ >= worklist.length());
|
||||
outstanding_ -= worklist.length();
|
||||
worklist.clear();
|
||||
|
||||
for (size_t i = finishedTasks_.length(); i > 0;) {
|
||||
HelperThreadState().remove(finishedTasks_, &i);
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
outstanding_--;
|
||||
}
|
||||
IonCompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock);
|
||||
MOZ_ASSERT(outstanding_ >= finished.length());
|
||||
outstanding_ -= finished.length();
|
||||
finished.clear();
|
||||
|
||||
uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs(lock);
|
||||
MOZ_ASSERT(outstanding_ >= numFailed);
|
||||
outstanding_ -= numFailed;
|
||||
|
||||
if (!outstanding_)
|
||||
break;
|
||||
@@ -95,10 +91,8 @@ ModuleGenerator::~ModuleGenerator()
|
||||
}
|
||||
}
|
||||
|
||||
if (parallelCompilationInProgressOnHelperThread_) {
|
||||
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
|
||||
HelperThreadState().wasmCompilationInProgress = false;
|
||||
}
|
||||
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
|
||||
HelperThreadState().wasmCompilationInProgress = false;
|
||||
} else {
|
||||
MOZ_ASSERT(!outstanding_);
|
||||
}
|
||||
@@ -214,9 +208,12 @@ ModuleGenerator::finishOutstandingTask()
|
||||
while (true) {
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
|
||||
if (!finishedTasks_.empty()) {
|
||||
if (HelperThreadState().wasmFailed(lock))
|
||||
return false;
|
||||
|
||||
if (!HelperThreadState().wasmFinishedList(lock).empty()) {
|
||||
outstanding_--;
|
||||
task = finishedTasks_.popCopy();
|
||||
task = HelperThreadState().wasmFinishedList(lock).popCopy();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -224,9 +221,6 @@ ModuleGenerator::finishOutstandingTask()
|
||||
}
|
||||
}
|
||||
|
||||
if (task->failed())
|
||||
return false;
|
||||
|
||||
return finishTask(task);
|
||||
}
|
||||
|
||||
@@ -371,9 +365,6 @@ ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
|
||||
bool
|
||||
ModuleGenerator::finishTask(IonCompileTask* task)
|
||||
{
|
||||
if (task->failed())
|
||||
return false;
|
||||
|
||||
const FuncBytes& func = task->func();
|
||||
FuncCompileResults& results = task->results();
|
||||
|
||||
@@ -865,25 +856,31 @@ ModuleGenerator::startFuncDefs()
|
||||
MOZ_ASSERT(!startedFuncDefs_);
|
||||
MOZ_ASSERT(!finishedFuncDefs_);
|
||||
|
||||
// Helper-thread initiated wasm compilations stay serialized so that we do
|
||||
// not end up with multiple helper threads blocking on other helper threads.
|
||||
// Main-thread compilations can still overlap because they drain their own
|
||||
// finished-task queue and do not steal tasks from other generators.
|
||||
// The wasmCompilationInProgress atomic ensures that there is only one
|
||||
// parallel compilation in progress at a time. In the special case of
|
||||
// asm.js, where the ModuleGenerator itself can be on a helper thread, this
|
||||
// avoids the possibility of deadlock since at most 1 helper thread will be
|
||||
// blocking on other helper threads and there are always >1 helper threads.
|
||||
// With wasm, this restriction could be relaxed by moving the worklist state
|
||||
// out of HelperThreadState since each independent compilation needs its own
|
||||
// worklist pair. Alternatively, the deadlock could be avoided by having the
|
||||
// ModuleGenerator thread make progress (on compile tasks) instead of
|
||||
// blocking.
|
||||
|
||||
GlobalHelperThreadState& threads = HelperThreadState();
|
||||
MOZ_ASSERT(threads.threadCount > 1);
|
||||
|
||||
uint32_t numTasks;
|
||||
if (CanUseExtraThreads() && (!CurrentHelperThread() ||
|
||||
threads.wasmCompilationInProgress.compareExchange(false, true))) {
|
||||
if (CanUseExtraThreads() && threads.wasmCompilationInProgress.compareExchange(false, true)) {
|
||||
#ifdef DEBUG
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
MOZ_ASSERT(!HelperThreadState().wasmFailed(lock));
|
||||
MOZ_ASSERT(HelperThreadState().wasmWorklist(lock).empty());
|
||||
MOZ_ASSERT(HelperThreadState().wasmFinishedList(lock).empty());
|
||||
}
|
||||
#endif
|
||||
parallel_ = true;
|
||||
parallelCompilationInProgressOnHelperThread_ = !!CurrentHelperThread();
|
||||
numTasks = 2 * threads.maxWasmCompilationThreads();
|
||||
} else {
|
||||
numTasks = 1;
|
||||
@@ -894,9 +891,6 @@ ModuleGenerator::startFuncDefs()
|
||||
for (size_t i = 0; i < numTasks; i++)
|
||||
tasks_.infallibleEmplaceBack(*shared_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
|
||||
|
||||
for (auto& task : tasks_)
|
||||
task.setFinishedList(&finishedTasks_);
|
||||
|
||||
if (!freeTasks_.reserve(numTasks))
|
||||
return false;
|
||||
for (size_t i = 0; i < numTasks; i++)
|
||||
|
||||
@@ -105,11 +105,9 @@ class MOZ_STACK_CLASS ModuleGenerator
|
||||
|
||||
// Parallel compilation
|
||||
bool parallel_;
|
||||
bool parallelCompilationInProgressOnHelperThread_;
|
||||
uint32_t outstanding_;
|
||||
IonCompileTaskVector tasks_;
|
||||
IonCompileTaskPtrVector freeTasks_;
|
||||
IonCompileTaskPtrVector finishedTasks_;
|
||||
|
||||
// Assertions
|
||||
DebugOnly<FunctionGenerator*> activeFuncDef_;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#define wasm_ion_compile_h
|
||||
|
||||
#include "jit/MacroAssembler.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "wasm/WasmTypes.h"
|
||||
|
||||
namespace js {
|
||||
@@ -108,8 +107,6 @@ class IonCompileTask
|
||||
UniqueFuncBytes func_;
|
||||
CompileMode mode_;
|
||||
Maybe<FuncCompileResults> results_;
|
||||
IonCompileTaskPtrVector* finishedList_;
|
||||
bool failed_;
|
||||
|
||||
IonCompileTask(const IonCompileTask&) = delete;
|
||||
IonCompileTask& operator=(const IonCompileTask&) = delete;
|
||||
@@ -117,26 +114,7 @@ class IonCompileTask
|
||||
public:
|
||||
IonCompileTask(const ModuleGeneratorData& mg, size_t defaultChunkSize)
|
||||
: mg_(mg), lifo_(defaultChunkSize), func_(nullptr), mode_(CompileMode::None)
|
||||
, finishedList_(nullptr)
|
||||
, failed_(false)
|
||||
{}
|
||||
|
||||
void setFinishedList(IonCompileTaskPtrVector* finishedList) {
|
||||
finishedList_ = finishedList;
|
||||
}
|
||||
|
||||
IonCompileTaskPtrVector* finishedList() const {
|
||||
MOZ_ASSERT(finishedList_);
|
||||
return finishedList_;
|
||||
}
|
||||
|
||||
void setFailed() {
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
bool failed() const {
|
||||
return failed_;
|
||||
}
|
||||
LifoAlloc& lifo() {
|
||||
return lifo_;
|
||||
}
|
||||
@@ -166,7 +144,6 @@ class IonCompileTask
|
||||
results_.reset();
|
||||
lifo_.releaseAll();
|
||||
mode_ = CompileMode::None;
|
||||
failed_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user