Compare commits

..
Author SHA1 Message Date
Moonchild baca5bb914 Issue #2390 - Part 1: Base implementation of Intl.ListFormat. 2026-05-16 19:48:16 +02:00
29 changed files with 1860 additions and 1709 deletions

No files matched your search

+5
View File
@@ -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;
+9 -6
View File
@@ -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}) {
+501
View File
@@ -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());
}
+67
View File
@@ -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 */
+262
View File
@@ -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
+285 -284
View File
@@ -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
View File
@@ -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:
-262
View File
@@ -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
-118
View File
@@ -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
+1
View File
@@ -28,6 +28,7 @@
\
_(IntlIsCollator) \
_(IntlIsDateTimeFormat) \
_(IntlIsListFormat) \
_(IntlIsNumberFormat) \
_(IntlIsPluralRules) \
_(IntlIsRelativeTimeFormat) \
+3
View File
@@ -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:
-30
View File
@@ -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
View File
@@ -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)
{}
+5
View File
@@ -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
View File
@@ -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)
{
-1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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',
+4
View File
@@ -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;
}
+2
View File
@@ -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") \
+1
View File
@@ -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
View File
@@ -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);
+2 -2
View File
@@ -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:
-6
View File
@@ -673,12 +673,6 @@ JSRuntime::traceSharedIntlData(JSTracer* trc)
void
JSRuntime::triggerActivityCallback(bool active)
{
if (active) {
gc.notifyJSExecutionStart();
} else {
gc.notifyJSExecutionEnd();
}
if (!activityCallback)
return;
+5
View File
@@ -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),
+31 -37
View File
@@ -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++)
-2
View File
@@ -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_;
-23
View File
@@ -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;
}
};