Compare commits

..
Author SHA1 Message Date
Basilisk-Dev 3ed4888c06 Safely parallelize GC background finalization
Parallelize background finalization across zones within each finalize phase while preserving phase ordering and falling back to serial work when no helper thread is idle.

Make compacting GC pointer updates schedule background work per zone and only when enough background-safe arenas exist.
2026-05-18 09:56:40 +02:00
Basilisk-Dev a6166027fa Fix unsafe GC multithreading changes 2026-05-18 09:56:32 +02:00
ownedbywuigi cc40df4933 close tab = quick gc pass 2026-05-18 09:56:17 +02:00
ownedbywuigi 7fe7fa64da Initial idle GC implementation 2026-05-18 09:55:25 +02:00
ownedbywuigi c6c5bcb1ee Implement parallel sweeping and compaction tasks for improved garbage collection performance 2026-05-18 09:54:22 +02:00
ownedbywuigi 028d533884 Implement BackgroundFinalizeTask for parallel garbage collection finalization 2026-05-18 09:53:21 +02:00
ownedbywuigi 1decb66e13 Add new GC sweep tasks for saved stacks, self-hosting scripts, and native iterators 2026-05-18 09:52:10 +02:00
ownedbywuigi 73d9a067bd Refactor wasm compilation handling 2026-05-18 09:52:05 +02:00
29 changed files with 1710 additions and 1861 deletions

No files matched your search

-5
View File
@@ -732,7 +732,6 @@ 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)),
@@ -816,7 +815,6 @@ 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)),
@@ -852,9 +850,6 @@ function getInternals(obj) {
case "DateTimeFormat":
internalProps = resolveDateTimeFormatInternals(internals.lazyData);
break;
case "ListFormat":
internalProps = resolveListFormatInternals(internals.lazyData);
break;
case "PluralRules":
internalProps = resolvePluralRulesInternals(internals.lazyData);
break;
+6 -9
View File
@@ -22,7 +22,6 @@
#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"
@@ -524,8 +523,6 @@ 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")) {
@@ -652,12 +649,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.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.
// 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.
bool isSupported = true;
for (auto kind : {SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
SupportedLocaleKind::NumberFormat}) {
-501
View File
@@ -1,501 +0,0 @@
/* -*- 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
@@ -1,67 +0,0 @@
/* -*- 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
@@ -1,262 +0,0 @@
/* 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
+284 -285
View File
@@ -1,286 +1,285 @@
/* -*- 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
/* -*- 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
#endif /* builtin_intl_SharedIntlData_h */
+15 -2
View File
@@ -13,6 +13,7 @@
#include "jsgc.h"
#include "gc/Heap.h"
#include "gc/IdleGC.h"
#include "gc/Nursery.h"
#include "gc/Statistics.h"
#include "gc/StoreBuffer.h"
@@ -650,6 +651,13 @@ 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() {
@@ -966,6 +974,8 @@ 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();
@@ -977,9 +987,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, size_t bgTaskCount);
void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds);
void updateAllCellPointers(MovingTracer* trc, Zone* zone);
void updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock);
void protectAndHoldArenas(Arena* arenaList);
@@ -1019,6 +1029,9 @@ class GCRuntime
GCSchedulingTunables tunables;
GCSchedulingState schedulingState;
/* Idle-time garbage collection manager. */
IdleGCManager idleGC;
MemProfiler mMemProfiler;
private:
+262
View File
@@ -0,0 +1,262 @@
/* -*- 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
@@ -0,0 +1,118 @@
/* -*- 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,7 +28,6 @@
\
_(IntlIsCollator) \
_(IntlIsDateTimeFormat) \
_(IntlIsListFormat) \
_(IntlIsNumberFormat) \
_(IntlIsPluralRules) \
_(IntlIsRelativeTimeFormat) \
-3
View File
@@ -14,7 +14,6 @@
#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"
@@ -114,8 +113,6 @@ 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,6 +1478,36 @@ 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,
+41 -7
View File
@@ -1719,6 +1719,40 @@ 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.
@@ -4870,15 +4904,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,11 +2877,6 @@ 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
+273 -57
View File
@@ -214,6 +214,7 @@
#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"
@@ -1570,7 +1571,7 @@ ArenaLists::prepareForIncrementalGC()
{
purge();
for (auto i : AllAllocKinds()) {
arenaLists[i].moveCursorToEnd();
arenaLists[i].moveCursorToEnd();
}
}
@@ -2117,7 +2118,7 @@ ArenasToUpdate::next(AutoLockHelperThreadState& lock)
// Find the next arena to update.
//
// This iterates through the GC thing kinds filtered by shouldProcessKind(),
// and then through thea arenas of that kind. All state is held in the
// and then through the 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)) {
@@ -2207,21 +2208,12 @@ UpdatePointersTask::run()
} // namespace gc
} // namespace js
static const size_t MinCellUpdateBackgroundTasks = 2;
static const size_t MinCellUpdateBackgroundTasks = 1;
static const size_t MaxCellUpdateBackgroundTasks = 8;
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) {
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
@@ -2233,6 +2225,34 @@ 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)
{
@@ -2253,10 +2273,15 @@ GCRuntime::updateTypeDescrObjects(MovingTracer* trc, Zone* zone)
}
void
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount)
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds)
{
AllocKinds fgKinds = bgTaskCount == 0 ? kinds : ForegroundUpdateKinds(kinds);
MOZ_ASSERT(trc);
AllocKinds fgKinds = 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);
@@ -2351,15 +2376,13 @@ GCRuntime::updateAllCellPointers(MovingTracer* trc, Zone* zone)
{
AutoDisableProxyCheck noProxyCheck(rt); // These checks assert when run in parallel.
size_t bgTaskCount = CellUpdateBackgroundTaskCount();
updateCellPointers(trc, zone, UpdatePhaseMisc, bgTaskCount);
updateCellPointers(trc, zone, UpdatePhaseMisc);
// Update TypeDescrs before all other objects as typed objects access these
// objects when we trace them.
updateTypeDescrObjects(trc, zone);
updateCellPointers(trc, zone, UpdatePhaseObjects, bgTaskCount);
updateCellPointers(trc, zone, UpdatePhaseObjects);
}
/*
@@ -2665,6 +2688,17 @@ 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)
{
@@ -3003,6 +3037,138 @@ 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)
{
@@ -3015,14 +3181,12 @@ GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks)
Arena* emptyArenas = nullptr;
FreeOp fop(nullptr);
for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) {
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);
}
}
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);
}
AutoLockGC lock(rt);
@@ -3581,14 +3745,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;
}
@@ -3654,7 +3818,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()) {
@@ -3670,7 +3834,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())
@@ -3762,7 +3926,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
bufferGrayRoots();
markCompartments();
}
return true;
}
@@ -3801,9 +3965,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))
@@ -3824,9 +3988,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))
@@ -4429,7 +4593,9 @@ MAKE_GC_SWEEP_TASK(SweepBaseShapesTask);
MAKE_GC_SWEEP_TASK(SweepInitialShapesTask);
MAKE_GC_SWEEP_TASK(SweepObjectGroupsTask);
MAKE_GC_SWEEP_TASK(SweepRegExpsTask);
MAKE_GC_SWEEP_TASK(SweepMiscTask);
MAKE_GC_SWEEP_TASK(SweepSavedStacksTask);
MAKE_GC_SWEEP_TASK(SweepSelfHostingScriptSourceTask);
MAKE_GC_SWEEP_TASK(SweepNativeIteratorsTask);
#undef MAKE_GC_SWEEP_TASK
/* virtual */ void
@@ -4462,15 +4628,27 @@ SweepRegExpsTask::run()
}
/* virtual */ void
SweepMiscTask::run()
SweepSavedStacksTask::run()
{
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) {
c->sweepSavedStacks();
c->sweepSelfHostingScriptSource();
c->sweepNativeIterators();
}
}
/* 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
GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase,
AutoLockHelperThreadState& locked)
@@ -4509,7 +4687,7 @@ PrepareWeakCacheTasks(JSRuntime* rt)
WeakCacheTaskVector out;
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
for (JS::WeakCache<void*>* cache : zone->weakCaches_) {
if (!out.append(SweepWeakCacheTask(rt, *cache))) {
if (!out.emplaceBack(rt, *cache)) {
SweepWeakCachesFromMainThread(rt);
return WeakCacheTaskVector();
}
@@ -4551,7 +4729,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
SweepCCWrappersTask sweepCCWrappersTask(rt);
SweepObjectGroupsTask sweepObjectGroupsTask(rt);
SweepRegExpsTask sweepRegExpsTask(rt);
SweepMiscTask sweepMiscTask(rt);
SweepSavedStacksTask sweepSavedStacksTask(rt);
SweepSelfHostingScriptSourceTask sweepSelfHostingScriptSourceTask(rt);
SweepNativeIteratorsTask sweepNativeIteratorsTask(rt);
WeakCacheTaskVector sweepCacheTasks = PrepareWeakCacheTasks(rt);
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
@@ -4599,7 +4779,9 @@ 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(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
startTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
startTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
startTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
for (auto& task : sweepCacheTasks)
startTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
}
@@ -4618,7 +4800,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
c->sweepJitCompartment(&fop);
c->sweepTemplateObjects();
}
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next())
zone->sweepWeakMaps();
@@ -4678,7 +4859,9 @@ 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(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
joinTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
joinTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
joinTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
for (auto& task : sweepCacheTasks)
joinTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
}
@@ -5005,7 +5188,7 @@ GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess&
// Reset phase index.
sweepPhaseIndex = 0;
endSweepingZoneGroup();
getNextZoneGroup();
if (!currentZoneGroup)
@@ -5107,6 +5290,7 @@ 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
@@ -5558,7 +5742,7 @@ gc::AbortReason
gc::IsIncrementalGCUnsafe(JSRuntime* rt)
{
MOZ_ASSERT(!rt->mainThread.suppressGC);
if (rt->keepAtoms())
return gc::AbortReason::KeepAtomsSet;
@@ -5579,7 +5763,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();
@@ -5587,7 +5771,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
return;
}
if (isTooMuchMalloc()) {
budget.makeUnlimited();
@@ -5827,6 +6011,12 @@ 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;
@@ -5835,6 +6025,12 @@ 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;
}
@@ -5842,15 +6038,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;
}
@@ -5872,13 +6068,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
@@ -5888,7 +6084,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
@@ -5912,8 +6108,16 @@ 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 (reason == JS::gcreason::ALLOC_TRIGGER)
if (isTabCloseReason(reason))
millis = 3;
else if (reason == JS::gcreason::ALLOC_TRIGGER)
millis = defaultSliceBudget();
else if (schedulingState.inHighFrequencyGCMode() && tunables.isDynamicMarkSliceEnabled())
millis = defaultSliceBudget() * IGC_MARK_SLICE_MULTIPLIER;
@@ -6001,6 +6205,18 @@ GCRuntime::notifyDidPaint()
interFrameGC = false;
}
void
GCRuntime::notifyJSExecutionStart()
{
idleGC.notifyJSExecutionStart();
}
void
GCRuntime::notifyJSExecutionEnd()
{
idleGC.notifyJSExecutionEnd();
}
static bool
ZonesSelected(JSRuntime* rt)
{
+1
View File
@@ -804,6 +804,7 @@ 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,7 +96,6 @@
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)) \
+1 -2
View File
@@ -126,7 +126,6 @@ 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',
@@ -173,6 +172,7 @@ 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,7 +746,6 @@ 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,10 +935,6 @@ 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,7 +118,6 @@
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, "") \
@@ -205,7 +204,6 @@
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,7 +29,6 @@
#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"
+11 -10
View File
@@ -478,7 +478,8 @@ 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++) {
@@ -486,7 +487,8 @@ 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)
@@ -968,8 +970,7 @@ GlobalHelperThreadState::maxGCParallelThreads() const
bool
GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
{
// Don't execute an wasm job if an earlier one failed.
if (wasmWorklist(lock).empty() || numWasmFailedJobs)
if (wasmWorklist(lock).empty())
return false;
// Honor the maximum allowed threads to compile wasm jobs at once,
@@ -1419,13 +1420,13 @@ HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
success = wasm::CompileFunction(task);
}
// On success, try to move work to the finished list.
if (success)
success = HelperThreadState().wasmFinishedList(locked).append(task);
// On failure, note the failure for harvesting by the parent.
// Append the task to the finished queue owned by its module generator.
if (!success)
HelperThreadState().noteWasmFailure(locked);
task->setFailed();
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!task->finishedList()->append(task))
oomUnsafe.crash("HelperThread::handleWasmWorkload");
// 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:
// For now, only allow a single parallel wasm compilation to happen at a
// time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc.
// Helper-thread initiated wasm compilations are serialized to avoid the
// deadlock scenario described in WasmGenerator.cpp.
mozilla::Atomic<bool> wasmCompilationInProgress;
private:
+6
View File
@@ -673,6 +673,12 @@ JSRuntime::traceSharedIntlData(JSTracer* trc)
void
JSRuntime::triggerActivityCallback(bool active)
{
if (active) {
gc.notifyJSExecutionStart();
} else {
gc.notifyJSExecutionEnd();
}
if (!activityCallback)
return;
-5
View File
@@ -26,7 +26,6 @@
#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"
@@ -2557,7 +2556,6 @@ 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),
@@ -2571,9 +2569,6 @@ 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),
+37 -31
View File
@@ -54,6 +54,7 @@ ModuleGenerator::ModuleGenerator(ImportVector&& imports)
lastPatchedCallsite_(0),
startOfUnpatchedCallsites_(0),
parallel_(false),
parallelCompilationInProgressOnHelperThread_(false),
outstanding_(0),
activeFuncDef_(nullptr),
startedFuncDefs_(false),
@@ -71,18 +72,21 @@ ModuleGenerator::~ModuleGenerator()
AutoLockHelperThreadState lock;
while (true) {
IonCompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock);
MOZ_ASSERT(outstanding_ >= worklist.length());
outstanding_ -= worklist.length();
worklist.clear();
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--;
}
}
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;
for (size_t i = finishedTasks_.length(); i > 0;) {
HelperThreadState().remove(finishedTasks_, &i);
MOZ_ASSERT(outstanding_ > 0);
outstanding_--;
}
if (!outstanding_)
break;
@@ -91,8 +95,10 @@ ModuleGenerator::~ModuleGenerator()
}
}
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
HelperThreadState().wasmCompilationInProgress = false;
if (parallelCompilationInProgressOnHelperThread_) {
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
HelperThreadState().wasmCompilationInProgress = false;
}
} else {
MOZ_ASSERT(!outstanding_);
}
@@ -208,12 +214,9 @@ ModuleGenerator::finishOutstandingTask()
while (true) {
MOZ_ASSERT(outstanding_ > 0);
if (HelperThreadState().wasmFailed(lock))
return false;
if (!HelperThreadState().wasmFinishedList(lock).empty()) {
if (!finishedTasks_.empty()) {
outstanding_--;
task = HelperThreadState().wasmFinishedList(lock).popCopy();
task = finishedTasks_.popCopy();
break;
}
@@ -221,6 +224,9 @@ ModuleGenerator::finishOutstandingTask()
}
}
if (task->failed())
return false;
return finishTask(task);
}
@@ -365,6 +371,9 @@ ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
bool
ModuleGenerator::finishTask(IonCompileTask* task)
{
if (task->failed())
return false;
const FuncBytes& func = task->func();
FuncCompileResults& results = task->results();
@@ -856,31 +865,25 @@ ModuleGenerator::startFuncDefs()
MOZ_ASSERT(!startedFuncDefs_);
MOZ_ASSERT(!finishedFuncDefs_);
// 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.
// 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.
GlobalHelperThreadState& threads = HelperThreadState();
MOZ_ASSERT(threads.threadCount > 1);
uint32_t numTasks;
if (CanUseExtraThreads() && threads.wasmCompilationInProgress.compareExchange(false, true)) {
if (CanUseExtraThreads() && (!CurrentHelperThread() ||
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;
@@ -891,6 +894,9 @@ 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,9 +105,11 @@ 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,6 +20,7 @@
#define wasm_ion_compile_h
#include "jit/MacroAssembler.h"
#include "vm/HelperThreads.h"
#include "wasm/WasmTypes.h"
namespace js {
@@ -107,6 +108,8 @@ class IonCompileTask
UniqueFuncBytes func_;
CompileMode mode_;
Maybe<FuncCompileResults> results_;
IonCompileTaskPtrVector* finishedList_;
bool failed_;
IonCompileTask(const IonCompileTask&) = delete;
IonCompileTask& operator=(const IonCompileTask&) = delete;
@@ -114,7 +117,26 @@ 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_;
}
@@ -144,6 +166,7 @@ class IonCompileTask
results_.reset();
lifo_.releaseAll();
mode_ = CompileMode::None;
failed_ = false;
}
};