Merge remote-tracking branch 'origin/custom' into custom-platform

This commit is contained in:
2026-05-19 13:56:29 +08:00
1895 changed files with 334773 additions and 715224 deletions
+38
View File
@@ -61,6 +61,44 @@ set_config('HAVE_YASM', have_yasm)
# Until the YASM variable is not necessary in old-configure.
add_old_configure_assignment('YASM', have_yasm)
# nasm detection
# ==============================================================
nasm = check_prog('NASM', ['nasm'], allow_missing=True)
@depends_if(nasm)
@checking('nasm version')
def nasm_version(nasm):
version = check_cmd_output(
nasm, '-v',
onerror=lambda: die('Failed to get nasm version.')
).splitlines()[0].split()[2]
return Version(version)
@depends(nasm, nasm_version)
def have_nasm(nasm, version):
if nasm and version >= Version('2.14'):
return True
set_config('HAVE_NASM', have_nasm)
@depends(have_nasm, target)
def nasm_asflags(have_nasm, target):
if have_nasm:
asflags = {
('OSX', 'x86'): '-f macho32',
('OSX', 'x86_64'): '-f macho64',
('WINNT', 'x86'): '-f win32',
('WINNT', 'x86_64'): '-f win64',
}.get((target.os, target.cpu), None)
if asflags is None:
if target.cpu == 'x86':
asflags = '-f elf32'
elif target.cpu == 'x86_64':
asflags = '-f elf64'
return asflags
set_config('NASM_ASFLAGS', nasm_asflags)
# Android NDK
# ==============================================================
+1 -1
View File
@@ -30,7 +30,7 @@ if CONFIG['MOZ_WEBM_ENCODER']:
external_dirs += ['media/libvpx']
if CONFIG['MOZ_AV1']:
external_dirs += ['media/libaom']
external_dirs += ['media/libdav1d']
external_dirs += ['media/libpng']
+1
View File
@@ -1005,6 +1005,7 @@ GK_ATOM(parameter, "parameter")
GK_ATOM(parent, "parent")
GK_ATOM(parentfocused, "parentfocused")
GK_ATOM(parsetype, "parsetype")
GK_ATOM(part, "part")
GK_ATOM(password, "password")
GK_ATOM(pattern, "pattern")
GK_ATOM(patternSeparator, "pattern-separator")
-1
View File
@@ -26,7 +26,6 @@ support-files =
image_yellow75.png
imagebitmap_bug1239300.js
imagebitmap_bug1239752.js
imagebitmap_extensions.html
imagebitmap_on_worker.js
imagebitmap_structuredclone.js
imagebitmap_structuredclone_iframe.html
+1 -1
View File
@@ -112,7 +112,7 @@ private:
DECL_MEDIA_PREF("media.ffmpeg.skip_loop_filter", FFmpegSkipLoopFilter, bool, false);
#endif
#ifdef MOZ_AV1
DECL_MEDIA_PREF("media.av1.enabled", AV1Enabled, bool, false);
DECL_MEDIA_PREF("media.av1.enabled", AV1Enabled, bool, true);
#endif
#ifdef XP_WIN
DECL_MEDIA_PREF("media.wmf.enabled", PDMWMFEnabled, bool, true);
-348
View File
@@ -1,348 +0,0 @@
/* -*- Mode: C++; tab-width: 2; 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 "AOMDecoder.h"
#include "MediaResult.h"
#include "TimeUnits.h"
#include "aom/aomdx.h"
#include "aom/aom_image.h"
#include "gfx2DGlue.h"
#include "mozilla/PodOperations.h"
#include "mozilla/SyncRunnable.h"
#include "nsError.h"
#include "prsystem.h"
#include <algorithm>
#undef LOG
#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
namespace mozilla {
using namespace gfx;
using namespace layers;
AOMDecoder::AOMDecoder(const CreateDecoderParams& aParams)
: mImageContainer(aParams.mImageContainer)
, mTaskQueue(aParams.mTaskQueue)
, mCallback(aParams.mCallback)
, mIsFlushing(false)
, mInfo(aParams.VideoConfig())
{
PodZero(&mCodec);
}
AOMDecoder::~AOMDecoder()
{
}
void
AOMDecoder::Shutdown()
{
aom_codec_destroy(&mCodec);
}
RefPtr<MediaDataDecoder::InitPromise>
AOMDecoder::Init()
{
int decode_threads = 2;
aom_codec_iface_t* dx = aom_codec_av1_dx();
if (mInfo.mDisplay.width >= 2048) {
decode_threads = 8;
}
else if (mInfo.mDisplay.width >= 1024) {
decode_threads = 4;
}
decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
aom_codec_dec_cfg_t config;
PodZero(&config);
config.threads = decode_threads;
config.w = config.h = 0; // set after decode
config.allow_lowbitdepth = true;
aom_codec_flags_t flags = 0;
if (!dx || aom_codec_dec_init(&mCodec, dx, &config, flags)) {
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
}
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
}
void
AOMDecoder::Flush()
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
mIsFlushing = true;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
// nothing to do for now.
});
SyncRunnable::DispatchToThread(mTaskQueue, r);
mIsFlushing = false;
}
// Ported from third_party/aom/tools_common.c.
static aom_codec_err_t
highbd_img_downshift(aom_image_t *dst, aom_image_t *src, int down_shift) {
int plane;
if (dst->d_w != src->d_w || dst->d_h != src->d_h)
return AOM_CODEC_INVALID_PARAM;
if (dst->x_chroma_shift != src->x_chroma_shift)
return AOM_CODEC_INVALID_PARAM;
if (dst->y_chroma_shift != src->y_chroma_shift)
return AOM_CODEC_INVALID_PARAM;
if (dst->fmt != (src->fmt & ~AOM_IMG_FMT_HIGHBITDEPTH))
return AOM_CODEC_INVALID_PARAM;
if (down_shift < 0)
return AOM_CODEC_INVALID_PARAM;
switch (dst->fmt) {
case AOM_IMG_FMT_I420:
case AOM_IMG_FMT_I422:
case AOM_IMG_FMT_I444:
break;
default:
return AOM_CODEC_INVALID_PARAM;
}
switch (src->fmt) {
case AOM_IMG_FMT_I42016:
case AOM_IMG_FMT_I42216:
case AOM_IMG_FMT_I44416:
break;
default:
// We don't support anything that's not 16 bit
return AOM_CODEC_UNSUP_BITSTREAM;
}
for (plane = 0; plane < 3; plane++) {
int w = src->d_w;
int h = src->d_h;
int x, y;
if (plane) {
w = (w + src->x_chroma_shift) >> src->x_chroma_shift;
h = (h + src->y_chroma_shift) >> src->y_chroma_shift;
}
for (y = 0; y < h; y++) {
uint16_t *p_src =
(uint16_t *)(src->planes[plane] + y * src->stride[plane]);
uint8_t *p_dst =
dst->planes[plane] + y * dst->stride[plane];
for (x = 0; x < w; x++) *p_dst++ = (*p_src++ >> down_shift) & 0xFF;
}
}
return AOM_CODEC_OK;
}
// UniquePtr dtor wrapper for aom_image_t.
struct AomImageFree {
void operator()(aom_image_t* img) { aom_img_free(img); }
};
MediaResult
AOMDecoder::DoDecode(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
#if defined(DEBUG)
NS_ASSERTION(IsKeyframe(*aSample) == aSample->mKeyframe,
"AOM Decode Keyframe error sample->mKeyframe and si.si_kf out of sync");
#endif
if (aom_codec_err_t r = aom_codec_decode(&mCodec, aSample->Data(), aSample->Size(), nullptr)) {
LOG("AOM Decode error: %s", aom_codec_err_to_string(r));
return MediaResult(
NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("AOM error decoding AV1 sample: %s", aom_codec_err_to_string(r)));
}
aom_codec_iter_t iter = nullptr;
aom_image_t *img;
UniquePtr<aom_image_t, AomImageFree> img8;
while ((img = aom_codec_get_frame(&mCodec, &iter))) {
// Track whether the underlying buffer is 8 or 16 bits per channel.
bool highbd = bool(img->fmt & AOM_IMG_FMT_HIGHBITDEPTH);
if (highbd) {
// Downsample images with more than 8 bits per channel.
aom_img_fmt_t fmt8 = static_cast<aom_img_fmt_t>(img->fmt ^ AOM_IMG_FMT_HIGHBITDEPTH);
img8.reset(aom_img_alloc(NULL, fmt8, img->d_w, img->d_h, 16));
if (img8 == nullptr) {
LOG("Couldn't allocate bitdepth reduction target!");
return MediaResult(
NS_ERROR_OUT_OF_MEMORY,
RESULT_DETAIL("Couldn't allocate conversion buffer for AV1 frame"));
}
if (aom_codec_err_t r = highbd_img_downshift(img8.get(), img, img->bit_depth - 8)) {
return MediaResult(
NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("Error converting AV1 frame to 8 bits: %s",
aom_codec_err_to_string(r)));
}
// img normally points to storage owned by mCodec, so it is not freed.
// To copy out the contents of img8 we can overwrite img with an alias.
// Since img is assigned at the start of the while loop and img8 is held
// outside that loop, the alias won't outlive the storage it points to.
img = img8.get();
highbd = false;
}
NS_ASSERTION(img->fmt == AOM_IMG_FMT_I420 ||
img->fmt == AOM_IMG_FMT_I42016 ||
img->fmt == AOM_IMG_FMT_I444 ||
img->fmt == AOM_IMG_FMT_I44416,
"AV1 image format not I420 or I444");
// Chroma shifts are rounded down as per the decoding examples in the SDK
VideoData::YCbCrBuffer b;
b.mPlanes[0].mData = img->planes[0];
b.mPlanes[0].mStride = img->stride[0];
b.mPlanes[0].mHeight = img->d_h;
b.mPlanes[0].mWidth = img->d_w;
b.mPlanes[0].mOffset = 0;
b.mPlanes[0].mSkip = highbd ? 1 : 0;
b.mPlanes[1].mData = img->planes[1];
b.mPlanes[1].mStride = img->stride[1];
b.mPlanes[1].mOffset = 0;
b.mPlanes[1].mSkip = highbd ? 1 : 0;
b.mPlanes[2].mData = img->planes[2];
b.mPlanes[2].mStride = img->stride[2];
b.mPlanes[2].mOffset = 0;
b.mPlanes[2].mSkip = highbd ? 1 : 0;
if (img->fmt == AOM_IMG_FMT_I420 ||
img->fmt == AOM_IMG_FMT_I42016) {
b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
} else if (img->fmt == AOM_IMG_FMT_I444) {
b.mPlanes[1].mHeight = img->d_h;
b.mPlanes[1].mWidth = img->d_w;
b.mPlanes[2].mHeight = img->d_h;
b.mPlanes[2].mWidth = img->d_w;
} else {
LOG("AOM Unknown image format");
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("AOM Unknown image format"));
}
switch (img->mc) {
case AOM_CICP_MC_BT_601:
b.mYUVColorSpace = YUVColorSpace::BT601;
break;
case AOM_CICP_MC_BT_709:
b.mYUVColorSpace = YUVColorSpace::BT709;
break;
case AOM_CICP_MC_IDENTITY:
b.mYUVColorSpace = YUVColorSpace::IDENTITY;
break;
default:
LOG("Unhandled colorspace %d", img->mc);
break;
}
b.mColorRange = img->range == AOM_CR_FULL_RANGE ? ColorRange::FULL
: ColorRange::LIMITED;
RefPtr<VideoData> v =
VideoData::CreateAndCopyData(mInfo,
mImageContainer,
aSample->mOffset,
aSample->mTime,
aSample->mDuration,
b,
aSample->mKeyframe,
aSample->mTimecode,
mInfo.ScaledImageRect(img->d_w,
img->d_h));
if (!v) {
LOG("Image allocation error source %ux%u display %ux%u picture %ux%u",
img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
mInfo.mImage.width, mInfo.mImage.height);
return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
}
mCallback->Output(v);
}
return NS_OK;
}
void
AOMDecoder::ProcessDecode(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
if (mIsFlushing) {
return;
}
MediaResult rv = DoDecode(aSample);
if (NS_FAILED(rv)) {
mCallback->Error(rv);
} else {
mCallback->InputExhausted();
}
}
void
AOMDecoder::Input(MediaRawData* aSample)
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
this, &AOMDecoder::ProcessDecode, aSample));
}
void
AOMDecoder::ProcessDrain()
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
mCallback->DrainComplete();
}
void
AOMDecoder::Drain()
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
mTaskQueue->Dispatch(NewRunnableMethod(this, &AOMDecoder::ProcessDrain));
}
/* static */
bool
AOMDecoder::IsAV1(const nsACString& aMimeType)
{
return aMimeType.EqualsLiteral("video/webm; codecs=av1") ||
aMimeType.EqualsLiteral("video/av1");
}
/* static */
bool
AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) {
aom_codec_stream_info_t info;
PodZero(&info);
aom_codec_peek_stream_info(aom_codec_av1_dx(),
aBuffer.Elements(),
aBuffer.Length(),
&info);
return bool(info.is_kf);
}
/* static */
nsIntSize
AOMDecoder::GetFrameSize(Span<const uint8_t> aBuffer) {
aom_codec_stream_info_t info;
PodZero(&info);
aom_codec_peek_stream_info(aom_codec_av1_dx(),
aBuffer.Elements(),
aBuffer.Length(),
&info);
return nsIntSize(info.w, info.h);
}
} // namespace mozilla
#undef LOG
-61
View File
@@ -1,61 +0,0 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#if !defined(AOMDecoder_h_)
#define AOMDecoder_h_
#include "PlatformDecoderModule.h"
#include "mozilla/Span.h"
#include <stdint.h>
#include "aom/aom_decoder.h"
namespace mozilla {
class AOMDecoder : public MediaDataDecoder
{
public:
explicit AOMDecoder(const CreateDecoderParams& aParams);
RefPtr<InitPromise> Init() override;
void Input(MediaRawData* aSample) override;
void Flush() override;
void Drain() override;
void Shutdown() override;
const char* GetDescriptionName() const override
{
return "libaom (AV1) video decoder";
}
// Return true if aMimeType is a one of the strings used
// by our demuxers to identify AV1 streams.
static bool IsAV1(const nsACString& aMimeType);
// Return true if a sample is a keyframe.
static bool IsKeyframe(Span<const uint8_t> aBuffer);
// Return the frame dimensions for a sample.
static nsIntSize GetFrameSize(Span<const uint8_t> aBuffer);
private:
~AOMDecoder();
void ProcessDecode(MediaRawData* aSample);
MediaResult DoDecode(MediaRawData* aSample);
void ProcessDrain();
const RefPtr<layers::ImageContainer> mImageContainer;
const RefPtr<TaskQueue> mTaskQueue;
MediaDataDecoderCallback* mCallback;
Atomic<bool> mIsFlushing;
// AOM decoder state
aom_codec_ctx_t mCodec;
const VideoInfo& mInfo;
};
} // namespace mozilla
#endif // AOMDecoder_h_
@@ -13,7 +13,7 @@
#include "TheoraDecoder.h"
#ifdef MOZ_AV1
#include "AOMDecoder.h"
#include "Dav1dDecoder.h"
#endif
namespace mozilla {
@@ -30,7 +30,7 @@ AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType,
TheoraDecoder::IsTheora(aMimeType);
#ifdef MOZ_AV1
if (MediaPrefs::AV1Enabled()) {
supports |= AOMDecoder::IsAV1(aMimeType);
supports |= Dav1dDecoder::IsAV1(aMimeType);
}
#endif
MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type",
@@ -47,9 +47,9 @@ AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
m = new VPXDecoder(aParams);
}
#ifdef MOZ_AV1
else if (AOMDecoder::IsAV1(aParams.mConfig.mMimeType) &&
else if (Dav1dDecoder::IsAV1(aParams.mConfig.mMimeType) &&
MediaPrefs::AV1Enabled()) {
m = new AOMDecoder(aParams);
m = new Dav1dDecoder(aParams);
}
#endif
else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) {
@@ -0,0 +1,376 @@
/* -*- Mode: C++; tab-width: 2; 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 "Dav1dDecoder.h"
#include "MediaResult.h"
#include "TimeUnits.h"
#include "dav1d/dav1d.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/PodOperations.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "nsError.h"
#include "prsystem.h"
#include <algorithm>
#include <errno.h>
#include <string.h>
#undef LOG
#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("Dav1dDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
namespace mozilla {
using namespace gfx;
using namespace layers;
Dav1dDecoder::Dav1dDecoder(const CreateDecoderParams& aParams)
: mImageContainer(aParams.mImageContainer)
, mTaskQueue(aParams.mTaskQueue)
, mCallback(aParams.mCallback)
, mIsFlushing(false)
, mDecoder(nullptr)
, mInfo(aParams.VideoConfig())
{
}
Dav1dDecoder::~Dav1dDecoder()
{
}
void
Dav1dDecoder::Shutdown()
{
if (mDecoder) {
dav1d_close(&mDecoder);
}
}
RefPtr<MediaDataDecoder::InitPromise>
Dav1dDecoder::Init()
{
int decodeThreads = 2;
if (mInfo.mDisplay.width >= 2048) {
decodeThreads = 8;
} else if (mInfo.mDisplay.width >= 1024) {
decodeThreads = 4;
}
decodeThreads = std::min(decodeThreads, PR_GetNumberOfProcessors());
Dav1dSettings settings;
dav1d_default_settings(&settings);
settings.n_threads = decodeThreads;
settings.logger.callback = nullptr;
if (dav1d_open(&mDecoder, &settings) < 0) {
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
}
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
}
void
Dav1dDecoder::Flush()
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
mIsFlushing = true;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
if (mDecoder) {
dav1d_flush(mDecoder);
}
});
SyncRunnable::DispatchToThread(mTaskQueue, r);
mIsFlushing = false;
}
static bool
GetPlaneSize(int aWidth, int aHeight, size_t* aSize)
{
CheckedInt<size_t> size = aWidth;
size *= aHeight;
if (!size.isValid()) {
return false;
}
*aSize = size.value();
return true;
}
static void
DownshiftPlane(uint8_t* aDst, int aDstStride, const uint8_t* aSrc,
ptrdiff_t aSrcStride, int aWidth, int aHeight, int aShift)
{
for (int y = 0; y < aHeight; y++) {
const uint16_t* src =
reinterpret_cast<const uint16_t*>(aSrc + y * aSrcStride);
uint8_t* dst = aDst + y * aDstStride;
for (int x = 0; x < aWidth; x++) {
dst[x] = (src[x] >> aShift) & 0xff;
}
}
}
MediaResult
Dav1dDecoder::OutputPicture(const Dav1dPicture& aPicture)
{
const int width = aPicture.p.w;
const int height = aPicture.p.h;
if (width <= 0 || height <= 0) {
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("dav1d returned invalid AV1 picture size"));
}
int chromaWidth = width;
int chromaHeight = height;
switch (aPicture.p.layout) {
case DAV1D_PIXEL_LAYOUT_I420:
chromaWidth = (width + 1) >> 1;
chromaHeight = (height + 1) >> 1;
break;
case DAV1D_PIXEL_LAYOUT_I422:
chromaWidth = (width + 1) >> 1;
chromaHeight = height;
break;
case DAV1D_PIXEL_LAYOUT_I444:
break;
default:
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("dav1d returned unsupported AV1 pixel layout: %d",
int(aPicture.p.layout)));
}
const int planeWidths[3] = { width, chromaWidth, chromaWidth };
const int planeHeights[3] = { height, chromaHeight, chromaHeight };
const ptrdiff_t srcStrides[3] = {
aPicture.stride[0],
aPicture.stride[1],
aPicture.stride[1]
};
uint8_t* planeData[3] = {
static_cast<uint8_t*>(aPicture.data[0]),
static_cast<uint8_t*>(aPicture.data[1]),
static_cast<uint8_t*>(aPicture.data[2])
};
uint32_t planeStrides[3] = { 0, 0, 0 };
UniquePtr<uint8_t[]> downshifted[3];
if (aPicture.p.bpc > 8) {
const int shift = aPicture.p.bpc - 8;
for (int plane = 0; plane < 3; plane++) {
size_t planeSize;
if (!GetPlaneSize(planeWidths[plane], planeHeights[plane], &planeSize)) {
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
RESULT_DETAIL("AV1 downshift plane size overflow"));
}
downshifted[plane] = MakeUniqueFallible<uint8_t[]>(planeSize);
if (!downshifted[plane]) {
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
RESULT_DETAIL("Couldn't allocate AV1 conversion buffer"));
}
DownshiftPlane(downshifted[plane].get(), planeWidths[plane],
planeData[plane], srcStrides[plane],
planeWidths[plane], planeHeights[plane], shift);
planeData[plane] = downshifted[plane].get();
planeStrides[plane] = planeWidths[plane];
}
} else {
for (int plane = 0; plane < 3; plane++) {
CheckedInt<uint32_t> stride(srcStrides[plane]);
if (srcStrides[plane] < 0 || !stride.isValid()) {
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("dav1d returned invalid AV1 stride"));
}
planeStrides[plane] = stride.value();
}
}
VideoData::YCbCrBuffer b;
for (int plane = 0; plane < 3; plane++) {
b.mPlanes[plane].mData = planeData[plane];
b.mPlanes[plane].mStride = planeStrides[plane];
b.mPlanes[plane].mHeight = planeHeights[plane];
b.mPlanes[plane].mWidth = planeWidths[plane];
b.mPlanes[plane].mOffset = 0;
b.mPlanes[plane].mSkip = 0;
}
if (aPicture.seq_hdr) {
switch (aPicture.seq_hdr->mtrx) {
case DAV1D_MC_BT601:
case DAV1D_MC_BT470BG:
b.mYUVColorSpace = YUVColorSpace::BT601;
break;
case DAV1D_MC_BT709:
b.mYUVColorSpace = YUVColorSpace::BT709;
break;
case DAV1D_MC_IDENTITY:
b.mYUVColorSpace = YUVColorSpace::IDENTITY;
break;
default:
LOG("Unhandled colorspace %d", aPicture.seq_hdr->mtrx);
break;
}
b.mColorRange = aPicture.seq_hdr->color_range ? ColorRange::FULL
: ColorRange::LIMITED;
}
const int64_t time =
aPicture.m.timestamp == INT64_MIN ? 0 : aPicture.m.timestamp;
const int64_t duration = aPicture.m.duration;
const int64_t offset = aPicture.m.offset;
const bool keyframe =
aPicture.frame_hdr &&
aPicture.frame_hdr->frame_type == DAV1D_FRAME_TYPE_KEY;
RefPtr<VideoData> v =
VideoData::CreateAndCopyData(mInfo,
mImageContainer,
offset,
time,
duration,
b,
keyframe,
time,
mInfo.ScaledImageRect(width, height));
if (!v) {
LOG("Image allocation error source %dx%d display %ux%u picture %ux%u",
width, height, mInfo.mDisplay.width, mInfo.mDisplay.height,
mInfo.mImage.width, mInfo.mImage.height);
return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
}
mCallback->Output(v);
return NS_OK;
}
MediaResult
Dav1dDecoder::DrainOutput()
{
while (true) {
Dav1dPicture picture;
PodZero(&picture);
const int res = dav1d_get_picture(mDecoder, &picture);
if (res == DAV1D_ERR(EAGAIN)) {
return NS_OK;
}
if (res < 0) {
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("dav1d error getting AV1 picture: %d",
res));
}
MediaResult rv = OutputPicture(picture);
dav1d_picture_unref(&picture);
if (NS_FAILED(rv)) {
return rv;
}
}
}
MediaResult
Dav1dDecoder::DoDecode(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
Dav1dData data;
PodZero(&data);
uint8_t* dst = dav1d_data_create(&data, aSample->Size());
if (!dst) {
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
RESULT_DETAIL("Couldn't allocate dav1d AV1 input buffer"));
}
memcpy(dst, aSample->Data(), aSample->Size());
data.m.timestamp = aSample->mTime;
data.m.duration = aSample->mDuration;
data.m.offset = aSample->mOffset;
data.m.size = aSample->Size();
while (data.sz) {
const int res = dav1d_send_data(mDecoder, &data);
if (res == DAV1D_ERR(EAGAIN)) {
MediaResult rv = DrainOutput();
if (NS_FAILED(rv)) {
dav1d_data_unref(&data);
return rv;
}
continue;
}
if (res < 0) {
dav1d_data_unref(&data);
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("dav1d error decoding AV1 sample: %d",
res));
}
}
dav1d_data_unref(&data);
return DrainOutput();
}
void
Dav1dDecoder::ProcessDecode(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
if (mIsFlushing) {
return;
}
MediaResult rv = DoDecode(aSample);
if (NS_FAILED(rv)) {
mCallback->Error(rv);
} else {
mCallback->InputExhausted();
}
}
void
Dav1dDecoder::Input(MediaRawData* aSample)
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
this, &Dav1dDecoder::ProcessDecode, aSample));
}
void
Dav1dDecoder::ProcessDrain()
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
MediaResult rv = DrainOutput();
if (NS_FAILED(rv)) {
mCallback->Error(rv);
} else {
mCallback->DrainComplete();
}
}
void
Dav1dDecoder::Drain()
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
mTaskQueue->Dispatch(NewRunnableMethod(this, &Dav1dDecoder::ProcessDrain));
}
/* static */
bool
Dav1dDecoder::IsAV1(const nsACString& aMimeType)
{
return aMimeType.EqualsLiteral("video/webm; codecs=av1") ||
aMimeType.EqualsLiteral("video/av1");
}
/* static */
nsIntSize
Dav1dDecoder::GetFrameSize(Span<const uint8_t> aBuffer)
{
Dav1dSequenceHeader seqHdr;
PodZero(&seqHdr);
if (dav1d_parse_sequence_header(&seqHdr,
aBuffer.Elements(),
aBuffer.Length()) < 0) {
return nsIntSize(0, 0);
}
return nsIntSize(seqHdr.max_width, seqHdr.max_height);
}
} // namespace mozilla
#undef LOG
@@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#if !defined(Dav1dDecoder_h_)
#define Dav1dDecoder_h_
#include "PlatformDecoderModule.h"
#include "mozilla/Span.h"
#include <stdint.h>
typedef struct Dav1dContext Dav1dContext;
typedef struct Dav1dPicture Dav1dPicture;
namespace mozilla {
class Dav1dDecoder : public MediaDataDecoder
{
public:
explicit Dav1dDecoder(const CreateDecoderParams& aParams);
RefPtr<InitPromise> Init() override;
void Input(MediaRawData* aSample) override;
void Flush() override;
void Drain() override;
void Shutdown() override;
const char* GetDescriptionName() const override
{
return "dav1d (AV1) video decoder";
}
// Return true if aMimeType is a one of the strings used
// by our demuxers to identify AV1 streams.
static bool IsAV1(const nsACString& aMimeType);
// Return the frame dimensions from a sequence header, when one is present.
static nsIntSize GetFrameSize(Span<const uint8_t> aBuffer);
private:
~Dav1dDecoder();
void ProcessDecode(MediaRawData* aSample);
MediaResult DoDecode(MediaRawData* aSample);
MediaResult DrainOutput();
MediaResult OutputPicture(const Dav1dPicture& aPicture);
void ProcessDrain();
const RefPtr<layers::ImageContainer> mImageContainer;
const RefPtr<TaskQueue> mTaskQueue;
MediaDataDecoderCallback* mCallback;
Atomic<bool> mIsFlushing;
Dav1dContext* mDecoder;
const VideoInfo& mInfo;
};
} // namespace mozilla
#endif // Dav1dDecoder_h_
+2 -2
View File
@@ -58,10 +58,10 @@ if CONFIG['MOZ_FFMPEG']:
if CONFIG['MOZ_AV1']:
EXPORTS += [
'agnostic/AOMDecoder.h',
'agnostic/Dav1dDecoder.h',
]
UNIFIED_SOURCES += [
'agnostic/AOMDecoder.cpp',
'agnostic/Dav1dDecoder.cpp',
]
if CONFIG['MOZ_APPLEMEDIA']:
-4
View File
@@ -4,9 +4,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Preferences.h"
#ifdef MOZ_AV1
#include "AOMDecoder.h"
#endif
#include "MediaPrefs.h"
#include "MediaDecoderStateMachine.h"
#include "WebMDemuxer.h"
@@ -136,4 +133,3 @@ WebMDecoder::GetMozDebugReaderData(nsAString& aString)
}
} // namespace mozilla
+10 -8
View File
@@ -8,7 +8,7 @@
#include "AbstractMediaDecoder.h"
#include "MediaResource.h"
#ifdef MOZ_AV1
#include "AOMDecoder.h"
#include "Dav1dDecoder.h"
#endif
#include "OpusDecoder.h"
#include "VPXDecoder.h"
@@ -711,7 +711,7 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSampl
break;
#ifdef MOZ_AV1
case NESTEGG_CODEC_AV1:
isKeyframe = AOMDecoder::IsKeyframe(sample);
isKeyframe = nestegg_packet_has_keyframe(holder->Packet()) == NESTEGG_PACKET_HAS_KEYFRAME_TRUE;
break;
#endif
case NESTEGG_CODEC_AVC1:
@@ -734,16 +734,18 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSampl
break;
#ifdef MOZ_AV1
case NESTEGG_CODEC_AV1:
dimensions = AOMDecoder::GetFrameSize(sample);
dimensions = Dav1dDecoder::GetFrameSize(sample);
break;
#endif
}
if (mLastSeenFrameSize.isSome()
&& (dimensions != mLastSeenFrameSize.value())) {
mInfo.mVideo.mDisplay = dimensions;
mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
if (dimensions.width > 0 && dimensions.height > 0) {
if (mLastSeenFrameSize.isSome()
&& (dimensions != mLastSeenFrameSize.value())) {
mInfo.mVideo.mDisplay = dimensions;
mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
}
mLastSeenFrameSize = Some(dimensions);
}
mLastSeenFrameSize = Some(dimensions);
}
}
}
+5
View File
@@ -24,6 +24,11 @@ if CONFIG['MOZ_WEBM_ENCODER']:
'WebMWriter.cpp',
]
if CONFIG['MOZ_AV1']:
LOCAL_INCLUDES += [
'/dom/media/platforms/agnostic',
]
CXXFLAGS += CONFIG['MOZ_LIBVPX_CFLAGS']
FINAL_LIBRARY = 'xul'
-1
View File
@@ -301,7 +301,6 @@ LoadContextOptions(const char* aPrefName, void* /* aClosure */)
.setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
.setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
.setStreams(GetWorkerPref<bool>(NS_LITERAL_CSTRING("streams")))
.setWeakRefs(GetWorkerPref<bool>(NS_LITERAL_CSTRING("weakrefs")))
.setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")))
.setArrayProtoValues(GetWorkerPref<bool>(
NS_LITERAL_CSTRING("array_prototype_values")));
-1
View File
@@ -35,7 +35,6 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, O
WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
WORKER_SIMPLE_PREF("dom.streams.enabled", StreamsEnabled, STREAMS_ENABLED)
WORKER_SIMPLE_PREF("javascript.options.weakrefs", WeakRefsEnabled, WEAKREFS_ENABLED)
WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
+28 -7
View File
@@ -823,16 +823,37 @@ nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
}
}
// The approach in WalkAllShadowRootHostRules seems reasonable on the surface and reminds me of what is done with mScopedRoot elsewhere. But something important is missing, either here or in the code that would normally use it.
void
nsBindingManager::WalkAllShadowRootHostRules(nsIStyleRuleProcessor::EnumFunc aFunc,
ElementDependentRuleProcessorData* aData)
{
aData->mTreeMatchContext.mOnlyMatchHostPseudo = true;
WalkAllRules(aFunc, aData, true);
aData->mTreeMatchContext.mOnlyMatchHostPseudo = false;
}
{
if (!mBoundContentSet) {
return;
}
bool oldOnlyMatchHostPseudo = aData->mTreeMatchContext.mOnlyMatchHostPseudo;
nsIContent* oldScopedRoot = aData->mTreeMatchContext.mScopedRoot;
aData->mTreeMatchContext.mOnlyMatchHostPseudo = true;
for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
nsIContent* boundContent = iter.Get()->GetKey();
ShadowRoot* shadowRoot = boundContent->GetShadowRoot();
if (!shadowRoot) {
continue;
}
nsXBLBinding* binding = shadowRoot->GetAssociatedBinding();
if (!binding) {
continue;
}
aData->mTreeMatchContext.mScopedRoot = boundContent;
binding->WalkRules(aFunc, aData);
}
aData->mTreeMatchContext.mScopedRoot = oldScopedRoot;
aData->mTreeMatchContext.mOnlyMatchHostPseudo = oldOnlyMatchHostPseudo;
}
nsresult
nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext,
+1 -1
View File
@@ -446,7 +446,7 @@ for (k = 0; k < 2; k++) {
while ((c = fgetc(f)) != '\n' && c != EOF);
/* issue warning if not a comment */
if (buf[0] != '%') {
fprintf(stderr, "Warning: skipping too long pattern (more than %lu chars)\n", sizeof(buf));
fprintf(stderr, "Warning: skipping too long pattern (more than %zu chars)\n", sizeof(buf));
}
continue;
}
+459 -40
View File
@@ -47,10 +47,12 @@
#include "builtin/AtomicsObject.h"
#include "mozilla/Atomics.h"
#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/Unused.h"
#include "builtin/Promise.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "jsnum.h"
@@ -58,7 +60,9 @@
#include "jit/AtomicOperations.h"
#include "jit/InlinableNatives.h"
#include "js/Class.h"
#include "vm/BigIntType.h"
#include "vm/GlobalObject.h"
#include "vm/HelperThreads.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "wasm/WasmInstance.h"
@@ -121,6 +125,68 @@ GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle<TypedArrayObject*> view,
return true;
}
static bool
IsWaitableTypedArray(Scalar::Type viewType)
{
return viewType == Scalar::Int32 || viewType == Scalar::BigInt64;
}
static uint32_t
GetWaiterByteOffset(Handle<TypedArrayObject*> view, uint32_t offset)
{
return view->byteOffset() + offset * TypedArrayElemSize(view->type());
}
static uint64_t
BigIntToRawBits(Scalar::Type viewType, BigInt* value)
{
if (viewType == Scalar::BigInt64)
return uint64_t(BigInt::toInt64(value));
MOZ_ASSERT(viewType == Scalar::BigUint64);
return BigInt::toUint64(value);
}
static bool
BigIntToRawBits(JSContext* cx, Scalar::Type viewType, HandleValue value, uint64_t* bits)
{
MOZ_ASSERT(Scalar::isBigIntType(viewType));
RootedBigInt bigint(cx, ToBigInt(cx, value));
if (!bigint)
return false;
*bits = BigIntToRawBits(viewType, bigint);
return true;
}
static bool
SetBigIntResult(JSContext* cx, Scalar::Type viewType, uint64_t bits, MutableHandleValue result)
{
MOZ_ASSERT(Scalar::isBigIntType(viewType));
BigInt* bigint = viewType == Scalar::BigInt64
? BigInt::createFromInt64(cx, mozilla::BitwiseCast<int64_t>(bits))
: BigInt::createFromUint64(cx, bits);
if (!bigint)
return false;
result.setBigInt(bigint);
return true;
}
static bool
WaitValueMatches(Handle<TypedArrayObject*> view, uint32_t offset, uint64_t expected)
{
SharedMem<void*> viewData = view->viewDataShared();
if (view->type() == Scalar::BigInt64) {
return jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<uint64_t*>() + offset) ==
expected;
}
return uint32_t(jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<int32_t*>() + offset)) ==
uint32_t(expected);
}
static int32_t
CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
SharedMem<void*> viewData, uint32_t offset, bool* badArrayType = nullptr)
@@ -191,6 +257,20 @@ js::atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp)
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
if (Scalar::isBigIntType(view->type())) {
uint64_t oldCandidate;
if (!BigIntToRawBits(cx, view->type(), oldv, &oldCandidate))
return false;
uint64_t newCandidate;
if (!BigIntToRawBits(cx, view->type(), newv, &newCandidate))
return false;
uint64_t result = jit::AtomicOperations::compareExchangeSeqCst(
view->viewDataShared().cast<uint64_t*>() + offset, oldCandidate, newCandidate);
return SetBigIntResult(cx, view->type(), result, r);
}
int32_t oldCandidate;
if (!ToInt32(cx, oldv, &oldCandidate))
return false;
@@ -259,6 +339,22 @@ js::atomics_load(JSContext* cx, unsigned argc, Value* vp)
r.setNumber(v);
return true;
}
case Scalar::BigInt64: {
int64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<int64_t*>() + offset);
BigInt* bigint = BigInt::createFromInt64(cx, v);
if (!bigint)
return false;
r.setBigInt(bigint);
return true;
}
case Scalar::BigUint64: {
uint64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint64_t*>() + offset);
BigInt* bigint = BigInt::createFromUint64(cx, v);
if (!bigint)
return false;
r.setBigInt(bigint);
return true;
}
default:
return ReportBadArrayType(cx);
}
@@ -337,6 +433,25 @@ ExchangeOrStore(JSContext* cx, unsigned argc, Value* vp)
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
if (Scalar::isBigIntType(view->type())) {
RootedBigInt bigint(cx, ToBigInt(cx, valv));
if (!bigint)
return false;
uint64_t value = BigIntToRawBits(view->type(), bigint);
if (op == DoStore) {
jit::AtomicOperations::storeSeqCst(view->viewDataShared().cast<uint64_t*>() + offset,
value);
r.setBigInt(bigint);
return true;
}
uint64_t result = jit::AtomicOperations::exchangeSeqCst(
view->viewDataShared().cast<uint64_t*>() + offset, value);
return SetBigIntResult(cx, view->type(), result, r);
}
double integerValue;
if (!ToInteger(cx, valv, &integerValue))
return false;
@@ -380,6 +495,24 @@ AtomicsBinop(JSContext* cx, HandleValue objv, HandleValue idxv, HandleValue valv
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
if (Scalar::isBigIntType(view->type())) {
uint64_t value;
if (!BigIntToRawBits(cx, view->type(), valv, &value))
return false;
SharedMem<uint64_t*> addr = view->viewDataShared().cast<uint64_t*>() + offset;
uint64_t old = jit::AtomicOperations::loadSeqCst(addr);
for (;;) {
uint64_t replacement = T::operate64(old, value);
uint64_t observed = jit::AtomicOperations::compareExchangeSeqCst(addr, old,
replacement);
if (observed == old)
return SetBigIntResult(cx, view->type(), old, r);
old = observed;
}
}
int32_t numberValue;
if (!ToInt32(cx, valv, &numberValue))
return false;
@@ -434,6 +567,7 @@ class PerformAdd
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x + y; }
static uint64_t operate64(uint64_t x, uint64_t y) { return x + y; }
};
bool
@@ -448,6 +582,7 @@ class PerformSub
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x - y; }
static uint64_t operate64(uint64_t x, uint64_t y) { return x - y; }
};
bool
@@ -462,6 +597,7 @@ class PerformAnd
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x & y; }
static uint64_t operate64(uint64_t x, uint64_t y) { return x & y; }
};
bool
@@ -476,6 +612,7 @@ class PerformOr
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x | y; }
static uint64_t operate64(uint64_t x, uint64_t y) { return x | y; }
};
bool
@@ -490,6 +627,7 @@ class PerformXor
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x ^ y; }
static uint64_t operate64(uint64_t x, uint64_t y) { return x ^ y; }
};
bool
@@ -693,11 +831,13 @@ js::atomics_cmpxchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t of
namespace js {
class AtomicsWaitAsyncTask;
// Represents one waiting worker.
//
// The type is declared opaque in SharedArrayObject.h. Instances of
// js::FutexWaiter are stack-allocated and linked onto a list across a
// call to FutexRuntime::wait().
// js::FutexWaiter are linked onto a list across a call to FutexRuntime::wait()
// or an async wait task.
//
// The 'waiters' field of the SharedArrayRawBuffer points to the highest
// priority waiter in the list, and lower priority nodes are linked through
@@ -711,14 +851,34 @@ class FutexWaiter
public:
FutexWaiter(uint32_t offset, JSRuntime* rt)
: offset(offset),
kind(Sync),
rt(rt),
asyncTask(nullptr),
lower_pri(nullptr),
back(nullptr)
{
}
uint32_t offset; // int32 element index within the SharedArrayBuffer
FutexWaiter(uint32_t offset, AtomicsWaitAsyncTask* asyncTask)
: offset(offset),
kind(Async),
rt(nullptr),
asyncTask(asyncTask),
lower_pri(nullptr),
back(nullptr)
{
}
bool isWaiting() const;
void notify();
uint32_t offset; // byte offset within the SharedArrayBuffer
enum WaiterKind {
Sync,
Async
} kind;
JSRuntime* rt; // The runtime of the waiter
AtomicsWaitAsyncTask* asyncTask; // The async waiter task, if any
FutexWaiter* lower_pri; // Lower priority nodes in circular doubly-linked list of waiters
FutexWaiter* back; // Other direction
};
@@ -742,8 +902,195 @@ class AutoLockFutexAPI
js::UniqueLock<js::Mutex>& unique() { return *unique_; }
};
static void
AddWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
{
if (FutexWaiter* waiters = sarb->waiters()) {
waiter->lower_pri = waiters;
waiter->back = waiters->back;
waiters->back->lower_pri = waiter;
waiters->back = waiter;
} else {
waiter->lower_pri = waiter->back = waiter;
sarb->setWaiters(waiter);
}
}
static void
RemoveWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
{
if (waiter->lower_pri == waiter) {
sarb->setWaiters(nullptr);
} else {
waiter->lower_pri->back = waiter->back;
waiter->back->lower_pri = waiter->lower_pri;
if (sarb->waiters() == waiter)
sarb->setWaiters(waiter->lower_pri);
}
waiter->lower_pri = nullptr;
waiter->back = nullptr;
}
class AtomicsWaitAsyncTask : public PromiseTask
{
enum class State {
Waiting,
Notified,
TimedOut
};
SharedArrayRawBuffer* sarb_;
FutexWaiter waiter_;
mozilla::Maybe<mozilla::TimeDuration> timeout_;
ConditionVariable cond_;
State state_;
bool isInWaiterList_;
public:
AtomicsWaitAsyncTask(JSContext* cx, Handle<PromiseObject*> promise,
SharedArrayRawBuffer* sarb, uint32_t offset,
mozilla::Maybe<mozilla::TimeDuration>& timeout)
: PromiseTask(cx, promise),
sarb_(sarb),
waiter_(offset, this),
timeout_(timeout),
state_(State::Waiting),
isInWaiterList_(false)
{
}
~AtomicsWaitAsyncTask() {
if (isInWaiterList_) {
AutoLockFutexAPI lock;
if (isInWaiterList_)
removeFromWaiterList();
}
sarb_->dropReference();
}
FutexWaiter* waiter() {
return &waiter_;
}
void setInWaiterList() {
isInWaiterList_ = true;
}
bool isWaiting() const {
return state_ == State::Waiting;
}
void notify() {
MOZ_ASSERT(isWaiting());
state_ = State::Notified;
cond_.notify_all();
}
void execute() override {
AutoLockFutexAPI lock;
const bool isTimed = timeout_.isSome();
auto finalEnd = timeout_.map([](mozilla::TimeDuration& timeout) {
return mozilla::TimeStamp::Now() + timeout;
});
auto maxSlice = mozilla::TimeDuration::FromSeconds(4000.0);
while (state_ == State::Waiting) {
if (isTimed) {
auto sliceEnd = finalEnd.map([&](mozilla::TimeStamp& finalEnd) {
auto sliceEnd = mozilla::TimeStamp::Now() + maxSlice;
if (finalEnd < sliceEnd)
sliceEnd = finalEnd;
return sliceEnd;
});
mozilla::Unused << cond_.wait_until(lock.unique(), *sliceEnd);
if (state_ == State::Waiting && mozilla::TimeStamp::Now() >= *finalEnd) {
state_ = State::TimedOut;
break;
}
} else {
cond_.wait(lock.unique());
}
}
if (isInWaiterList_)
removeFromWaiterList();
}
private:
void removeFromWaiterList() {
MOZ_ASSERT(isInWaiterList_);
RemoveWaiter(sarb_, &waiter_);
isInWaiterList_ = false;
}
bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
RootedValue result(cx);
if (state_ == State::TimedOut)
result.setString(cx->names().futexTimedOut);
else
result.setString(cx->names().futexOK);
return PromiseObject::resolve(cx, promise, result);
}
};
bool
FutexWaiter::isWaiting() const
{
if (kind == Sync)
return rt->fx.isWaiting();
return asyncTask->isWaiting();
}
void
FutexWaiter::notify()
{
if (kind == Sync) {
rt->fx.notify(FutexRuntime::NotifyExplicit);
return;
}
asyncTask->notify();
}
} // namespace js
static bool
GetWaitTimeout(JSContext* cx, HandleValue timeoutv,
mozilla::Maybe<mozilla::TimeDuration>* timeout)
{
timeout->reset();
if (!timeoutv.isUndefined()) {
double timeout_ms;
if (!ToNumber(cx, timeoutv, &timeout_ms))
return false;
if (!mozilla::IsNaN(timeout_ms)) {
if (timeout_ms < 0)
*timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
else if (!mozilla::IsInfinite(timeout_ms))
*timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
}
}
return true;
}
static bool
CreateWaitAsyncResult(JSContext* cx, bool isAsync, HandleValue value, MutableHandleValue rval)
{
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!obj)
return false;
RootedValue asyncValue(cx, BooleanValue(isAsync));
if (!NativeDefineDataProperty(cx, obj, cx->names().async, asyncValue, JSPROP_ENUMERATE))
return false;
if (!NativeDefineDataProperty(cx, obj, cx->names().value, value, JSPROP_ENUMERATE))
return false;
rval.setObject(*obj);
return true;
}
bool
js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
{
@@ -759,26 +1106,24 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
Rooted<TypedArrayObject*> view(cx, nullptr);
if (!GetSharedTypedArray(cx, objv, &view))
return false;
if (view->type() != Scalar::Int32)
if (!IsWaitableTypedArray(view->type()))
return ReportBadArrayType(cx);
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
int32_t value;
if (!ToInt32(cx, valv, &value))
return false;
mozilla::Maybe<mozilla::TimeDuration> timeout;
if (!timeoutv.isUndefined()) {
double timeout_ms;
if (!ToNumber(cx, timeoutv, &timeout_ms))
uint64_t value = 0;
if (view->type() == Scalar::BigInt64) {
if (!BigIntToRawBits(cx, view->type(), valv, &value))
return false;
if (!mozilla::IsNaN(timeout_ms)) {
if (timeout_ms < 0)
timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
else if (!mozilla::IsInfinite(timeout_ms))
timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
}
} else {
int32_t int32Value;
if (!ToInt32(cx, valv, &int32Value))
return false;
value = uint32_t(int32Value);
}
mozilla::Maybe<mozilla::TimeDuration> timeout;
if (!GetWaitTimeout(cx, timeoutv, &timeout))
return false;
if (!rt->fx.canWait())
return ReportCannotWait(cx);
@@ -787,8 +1132,7 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
// and it provides the necessary memory fence.
AutoLockFutexAPI lock;
SharedMem<int32_t*> addr = view->viewDataShared().cast<int32_t*>() + offset;
if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
if (!WaitValueMatches(view, offset, value)) {
r.setString(cx->names().futexNotEqual);
return true;
}
@@ -796,16 +1140,8 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
SharedArrayRawBuffer* sarb = sab->rawBufferObject();
FutexWaiter w(offset, rt);
if (FutexWaiter* waiters = sarb->waiters()) {
w.lower_pri = waiters;
w.back = waiters->back;
waiters->back->lower_pri = &w;
waiters->back = &w;
} else {
w.lower_pri = w.back = &w;
sarb->setWaiters(&w);
}
FutexWaiter w(GetWaiterByteOffset(view, offset), rt);
AddWaiter(sarb, &w);
FutexRuntime::WaitResult result = FutexRuntime::FutexOK;
bool retval = rt->fx.wait(cx, lock.unique(), timeout, &result);
@@ -820,17 +1156,98 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
}
}
if (w.lower_pri == &w) {
sarb->setWaiters(nullptr);
} else {
w.lower_pri->back = w.back;
w.back->lower_pri = w.lower_pri;
if (sarb->waiters() == &w)
sarb->setWaiters(w.lower_pri);
}
RemoveWaiter(sarb, &w);
return retval;
}
bool
js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
HandleValue objv = args.get(0);
HandleValue idxv = args.get(1);
HandleValue valv = args.get(2);
HandleValue timeoutv = args.get(3);
MutableHandleValue r = args.rval();
Rooted<TypedArrayObject*> view(cx, nullptr);
if (!GetSharedTypedArray(cx, objv, &view))
return false;
if (!IsWaitableTypedArray(view->type()))
return ReportBadArrayType(cx);
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
uint64_t value = 0;
if (view->type() == Scalar::BigInt64) {
if (!BigIntToRawBits(cx, view->type(), valv, &value))
return false;
} else {
int32_t int32Value;
if (!ToInt32(cx, valv, &int32Value))
return false;
value = uint32_t(int32Value);
}
mozilla::Maybe<mozilla::TimeDuration> timeout;
if (!GetWaitTimeout(cx, timeoutv, &timeout))
return false;
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
if (!promise)
return false;
RootedValue promiseValue(cx, ObjectValue(*promise));
RootedValue result(cx);
if (!CreateWaitAsyncResult(cx, true, promiseValue, &result))
return false;
Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
SharedArrayRawBuffer* sarb = sab->rawBufferObject();
if (!sarb->addReference()) {
JS_ReportErrorASCII(cx, "Reference count overflow on SharedArrayBuffer");
return false;
}
auto task = cx->make_unique<AtomicsWaitAsyncTask>(cx, promise, sarb, offset, timeout);
if (!task) {
sarb->dropReference();
return false;
}
bool isAsync = false;
RootedValue immediateResult(cx);
{
AutoLockFutexAPI lock;
if (!WaitValueMatches(view, offset, value)) {
immediateResult.setString(cx->names().futexNotEqual);
} else if (timeout.isSome() && timeout->ToMilliseconds() == 0.0) {
immediateResult.setString(cx->names().futexTimedOut);
} else {
if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback ||
!CanUseExtraThreads())
{
JS_ReportErrorASCII(cx, "Atomics.waitAsync not supported in this runtime.");
return false;
}
task->waiter()->offset = GetWaiterByteOffset(view, offset);
AddWaiter(sarb, task->waiter());
task->setInWaiterList();
isAsync = true;
}
}
if (!isAsync)
return CreateWaitAsyncResult(cx, false, immediateResult, r);
if (!StartPromiseTask(cx, Move(task)))
return false;
r.set(result);
return true;
}
bool
js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
{
@@ -843,11 +1260,12 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
Rooted<TypedArrayObject*> view(cx, nullptr);
if (!GetSharedTypedArray(cx, objv, &view))
return false;
if (view->type() != Scalar::Int32)
if (!IsWaitableTypedArray(view->type()))
return ReportBadArrayType(cx);
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
offset = GetWaiterByteOffset(view, offset);
double count;
if (countv.isUndefined()) {
count = mozilla::PositiveInfinity<double>();
@@ -870,9 +1288,9 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
do {
FutexWaiter* c = iter;
iter = iter->lower_pri;
if (c->offset != offset || !c->rt->fx.isWaiting())
if (c->offset != offset || !c->isWaiting())
continue;
c->rt->fx.notify(FutexRuntime::NotifyExplicit);
c->notify();
++woken;
--count;
} while (count > 0 && iter != waiters);
@@ -1106,6 +1524,7 @@ const JSFunctionSpec AtomicsMethods[] = {
JS_INLINABLE_FN("xor", atomics_xor, 3,0, AtomicsXor),
JS_INLINABLE_FN("isLockFree", atomics_isLockFree, 1,0, AtomicsIsLockFree),
JS_FN("wait", atomics_wait, 4,0),
JS_FN("waitAsync", atomics_waitAsync, 4,0),
JS_FN("notify", atomics_notify, 3,0),
JS_FN("wake", atomics_notify, 3,0), //Legacy name
JS_FS_END
+1
View File
@@ -35,6 +35,7 @@ MOZ_MUST_USE bool atomics_or(JSContext* cx, unsigned argc, Value* vp);
MOZ_MUST_USE bool atomics_xor(JSContext* cx, unsigned argc, Value* vp);
MOZ_MUST_USE bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp);
MOZ_MUST_USE bool atomics_wait(JSContext* cx, unsigned argc, Value* vp);
MOZ_MUST_USE bool atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp);
MOZ_MUST_USE bool atomics_notify(JSContext* cx, unsigned argc, Value* vp);
/* asm.js callouts */
@@ -0,0 +1,608 @@
/* -*- 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/. */
#include "builtin/FinalizationRegistryObject.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "builtin/WeakRefObject.h"
#include "gc/Nursery.h"
#include "gc/Tracer.h"
#include "vm/EqualityOperations.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/Symbol.h"
#include "jswrapper.h"
#include "jsobjinlines.h"
#include "vm/Interpreter-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
enum class WeakCellKind {
Empty,
Object,
Symbol
};
static JSObject*
NormalizeWeakObject(JSObject* obj)
{
if (JSObject* unwrapped = CheckedUnwrap(obj, /* stopAtWindowProxy = */ false))
return unwrapped;
return obj;
}
static bool
GetPrototypeFromFinalizationRegistryConstructor(JSContext* cx, HandleObject newTarget,
MutableHandleObject proto)
{
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
return false;
if (proto)
return true;
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
if (!realmObject)
return false;
{
JSAutoCompartment ac(cx, realmObject);
Rooted<GlobalObject*> global(cx, &realmObject->global());
if (!GlobalObject::ensureConstructor(cx, global, JSProto_FinalizationRegistry))
return false;
proto.set(&global->getPrototype(JSProto_FinalizationRegistry).toObject());
}
return cx->compartment()->wrap(cx, proto);
}
struct WeakCell {
WeakCellKind kind;
WeakRef<JSObject*> object;
WeakRef<JS::Symbol*> symbol;
WeakCell() : kind(WeakCellKind::Empty), object(nullptr), symbol(nullptr) {}
explicit WeakCell(JSObject* obj) : kind(WeakCellKind::Object), object(obj), symbol(nullptr) {}
explicit WeakCell(JS::Symbol* sym) : kind(WeakCellKind::Symbol), object(nullptr), symbol(sym) {}
void clear() {
kind = WeakCellKind::Empty;
object = nullptr;
symbol = nullptr;
}
bool isEmpty() const { return kind == WeakCellKind::Empty; }
bool isDead() const {
if (kind == WeakCellKind::Object)
return !object.unbarrieredGet();
if (kind == WeakCellKind::Symbol)
return !symbol.unbarrieredGet();
return false;
}
bool sameValue(HandleValue value) const {
if (kind == WeakCellKind::Object)
return value.isObject() && object.get() == NormalizeWeakObject(&value.toObject());
if (kind == WeakCellKind::Symbol)
return value.isSymbol() && symbol.get() == value.toSymbol();
return false;
}
void trace(JSTracer* trc, const char* name) {
if (kind == WeakCellKind::Object) {
JSObject* target = object.unbarrieredGet();
if (!target)
return;
if (IsInsideNursery(target)) {
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
} else {
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
return;
TraceWeakEdge(trc, &object, name);
}
} else if (kind == WeakCellKind::Symbol) {
JS::Symbol* target = symbol.unbarrieredGet();
if (target) {
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
return;
TraceWeakEdge(trc, &symbol, name);
}
}
}
void traceIfZoneIsCollecting(JSTracer* trc, const char* name) {
if (kind == WeakCellKind::Object) {
JSObject* target = object.unbarrieredGet();
if (!target)
return;
if (IsInsideNursery(target)) {
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
} else if (target->asTenured().zone()->isCollecting()) {
TraceWeakEdge(trc, &object, name);
}
} else if (kind == WeakCellKind::Symbol) {
JS::Symbol* target = symbol.unbarrieredGet();
if (target && target->asTenured().zone()->isCollecting())
TraceWeakEdge(trc, &symbol, name);
}
}
};
struct FinalizationRecord {
WeakCell target;
JS::Heap<Value> heldValue;
WeakCell unregisterToken;
bool active;
bool queued;
FinalizationRecord(HandleValue targetValue, HandleValue heldValue,
HandleValue unregisterTokenValue)
: heldValue(heldValue),
active(true),
queued(false)
{
if (targetValue.isObject())
target = WeakCell(NormalizeWeakObject(&targetValue.toObject()));
else
target = WeakCell(targetValue.toSymbol());
if (unregisterTokenValue.isObject())
unregisterToken = WeakCell(NormalizeWeakObject(&unregisterTokenValue.toObject()));
else if (unregisterTokenValue.isSymbol())
unregisterToken = WeakCell(unregisterTokenValue.toSymbol());
}
void trace(JSTracer* trc) {
if (active || queued)
JS::TraceEdge(trc, &heldValue, "FinalizationRegistry held value");
if (active) {
target.trace(trc, "FinalizationRegistry target");
if (!unregisterToken.isEmpty())
unregisterToken.trace(trc, "FinalizationRegistry unregister token");
}
}
void traceWeakEdgesForCollectedZones(JSTracer* trc) {
if (!active)
return;
target.traceIfZoneIsCollecting(trc, "FinalizationRegistry target");
if (!unregisterToken.isEmpty())
unregisterToken.traceIfZoneIsCollecting(trc,
"FinalizationRegistry unregister token");
}
bool targetIsDead() const {
return active && target.isDead();
}
bool matchesToken(HandleValue token) const {
return active && !queued && !unregisterToken.isEmpty() &&
unregisterToken.sameValue(token);
}
void queueForCleanup() {
MOZ_ASSERT(active);
MOZ_ASSERT(!queued);
active = false;
queued = true;
target.clear();
unregisterToken.clear();
}
void clear() {
active = false;
queued = false;
target.clear();
unregisterToken.clear();
heldValue.setUndefined();
}
};
using FinalizationRecordVector = Vector<FinalizationRecord*, 0, SystemAllocPolicy>;
struct FinalizationRegistryObject::Data {
JS::Heap<JSObject*> cleanupCallback;
JS::Heap<JSObject*> cleanupJob;
FinalizationRecordVector records;
FinalizationRecordVector cleanupQueue;
bool queuedForCleanup;
explicit Data(JSObject* cleanupCallback)
: cleanupCallback(cleanupCallback),
cleanupJob(nullptr),
records(SystemAllocPolicy()),
cleanupQueue(SystemAllocPolicy()),
queuedForCleanup(false)
{}
~Data() {
for (size_t i = 0; i < records.length(); i++)
js_delete(records[i]);
}
void trace(JSTracer* trc) {
JS::TraceEdge(trc, &cleanupCallback, "FinalizationRegistry cleanup callback");
if (cleanupJob.unbarrieredGet())
JS::TraceEdge(trc, &cleanupJob, "FinalizationRegistry cleanup job");
for (size_t i = 0; i < records.length(); i++)
records[i]->trace(trc);
}
bool appendRecord(FinalizationRecord* record) {
return records.append(record);
}
bool appendCleanupRecord(FinalizationRecord* record) {
return cleanupQueue.append(record);
}
void compactRecords() {
for (size_t i = 0; i < records.length();) {
FinalizationRecord* record = records[i];
if (!record->active && !record->queued) {
js_delete(record);
records.erase(records.begin() + i);
continue;
}
i++;
}
}
void unregister(HandleValue token, bool* removed) {
*removed = false;
for (size_t i = 0; i < records.length(); i++) {
FinalizationRecord* record = records[i];
if (record->matchesToken(token)) {
record->clear();
*removed = true;
}
}
compactRecords();
}
};
static FinalizationRegistryObject::Data*
GetData(JSObject* obj)
{
return obj->as<FinalizationRegistryObject>().getData();
}
static MOZ_ALWAYS_INLINE bool
IsFinalizationRegistry(HandleValue v)
{
return v.isObject() && v.toObject().is<FinalizationRegistryObject>();
}
static bool
FinalizationRegistry_register_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
if (!CanBeHeldWeakly(args.get(0))) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_TARGET);
return false;
}
RootedValue target(cx, args[0]);
RootedValue heldValue(cx, args.get(1));
bool same;
if (!SameValue(cx, target, heldValue, &same))
return false;
if (same) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE);
return false;
}
RootedValue unregisterToken(cx, args.get(2));
if (!CanBeHeldWeakly(unregisterToken)) {
if (!unregisterToken.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
return false;
}
unregisterToken.setUndefined();
}
FinalizationRecord* record = cx->new_<FinalizationRecord>(target, heldValue, unregisterToken);
if (!record)
return false;
auto* data = GetData(&args.thisv().toObject());
if (!data->appendRecord(record)) {
js_delete(record);
ReportOutOfMemory(cx);
return false;
}
args.rval().setUndefined();
return true;
}
static bool
FinalizationRegistry_unregister_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
RootedValue unregisterToken(cx, args.get(0));
if (!CanBeHeldWeakly(unregisterToken)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
return false;
}
bool removed = false;
auto* data = GetData(&args.thisv().toObject());
data->unregister(unregisterToken, &removed);
args.rval().setBoolean(removed);
return true;
}
const JSPropertySpec FinalizationRegistryObject::properties[] = {
JS_PS_END
};
const JSFunctionSpec FinalizationRegistryObject::methods[] = {
JS_FN("register", FinalizationRegistryObject::register_, 2, 0),
JS_FN("unregister", FinalizationRegistryObject::unregister, 1, 0),
JS_FS_END
};
static JSObject*
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj, bool defineMembers)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!proto)
return nullptr;
RootedFunction ctor(cx, GlobalObject::createConstructor(cx, FinalizationRegistryObject::construct,
ClassName(JSProto_FinalizationRegistry, cx), 1));
if (!ctor)
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (defineMembers) {
if (!DefinePropertiesAndFunctions(cx, proto, FinalizationRegistryObject::properties,
FinalizationRegistryObject::methods)) {
return nullptr;
}
if (!DefineToStringTag(cx, proto, cx->names().FinalizationRegistry))
return nullptr;
}
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_FinalizationRegistry, ctor, proto))
return nullptr;
return proto;
}
/* static */ FinalizationRegistryObject*
FinalizationRegistryObject::create(JSContext* cx, HandleObject cleanupCallback,
HandleObject proto /* = nullptr */)
{
Rooted<FinalizationRegistryObject*> obj(cx,
NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
if (!obj)
return nullptr;
Data* data = cx->new_<Data>(cleanupCallback);
if (!data)
return nullptr;
obj->setPrivate(data);
RootedAtom funName(cx, cx->names().empty);
RootedFunction cleanupJob(cx, NewNativeFunction(cx, FinalizationRegistryObject::cleanupJob, 0,
funName, gc::AllocKind::FUNCTION_EXTENDED,
GenericObject));
if (!cleanupJob)
return nullptr;
cleanupJob->setExtendedSlot(0, ObjectValue(*obj));
data->cleanupJob = cleanupJob;
if (!cx->zone()->finalizationRegistries.append(WeakRef<JSObject*>(obj))) {
ReportOutOfMemory(cx);
return nullptr;
}
return obj;
}
/* static */ bool
FinalizationRegistryObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry"))
return false;
RootedValue cleanupCallbackValue(cx, args.get(0));
RootedObject cleanupCallback(cx, ValueToCallable(cx, cleanupCallbackValue, 0, NO_CONSTRUCT));
if (!cleanupCallback)
return false;
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
if (!GetPrototypeFromFinalizationRegistryConstructor(cx, newTarget, &proto))
return false;
Rooted<FinalizationRegistryObject*> obj(cx,
FinalizationRegistryObject::create(cx, cleanupCallback, proto));
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
/* static */ bool
FinalizationRegistryObject::register_(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_register_impl>(cx, args);
}
/* static */ bool
FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_unregister_impl>(cx, args);
}
/* static */ bool
FinalizationRegistryObject::cleanupJob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction job(cx, &args.callee().as<JSFunction>());
Rooted<FinalizationRegistryObject*> registry(cx,
&job->getExtendedSlot(0).toObject().as<FinalizationRegistryObject>());
Data* data = registry->getData();
if (!data) {
args.rval().setUndefined();
return true;
}
data->queuedForCleanup = false;
RootedValue callback(cx, ObjectValue(*data->cleanupCallback.get()));
RootedValue heldValue(cx);
RootedValue ignored(cx);
RootedValue undefined(cx, UndefinedValue());
while (data->cleanupQueue.length() > 0) {
FinalizationRecord* record = data->cleanupQueue[0];
data->cleanupQueue.erase(data->cleanupQueue.begin());
if (!record->queued) {
data->compactRecords();
continue;
}
heldValue.set(record->heldValue.get());
record->clear();
data->compactRecords();
if (!Call(cx, callback, undefined, heldValue, &ignored))
return false;
}
args.rval().setUndefined();
return true;
}
/* static */ void
FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj)
{
if (Data* data = GetData(obj))
data->trace(trc);
}
/* static */ void
FinalizationRegistryObject::finalize(FreeOp* fop, JSObject* obj)
{
if (Data* data = GetData(obj))
fop->delete_(data);
}
void
FinalizationRegistryObject::traceWeakEdgesForCollectedZones(JSTracer* trc)
{
Data* data = getData();
if (!data)
return;
for (size_t i = 0; i < data->records.length(); i++)
data->records[i]->traceWeakEdgesForCollectedZones(trc);
}
void
FinalizationRegistryObject::sweepAfterGC(JSRuntime* rt)
{
Data* data = getData();
if (!data)
return;
bool needsCleanupJob = false;
for (size_t i = 0; i < data->records.length(); i++) {
FinalizationRecord* record = data->records[i];
if (!record->targetIsDead())
continue;
record->queueForCleanup();
if (!data->appendCleanupRecord(record)) {
AutoEnterOOMUnsafeRegion oomUnsafe;
oomUnsafe.crash("queueing FinalizationRegistry cleanup record");
}
needsCleanupJob = true;
}
if (needsCleanupJob && !data->queuedForCleanup) {
JSContext* cx = rt->contextFromMainThread();
RootedObject cleanupJob(cx, data->cleanupJob.unbarrieredGet());
if (!rt->enqueueFinalizationRegistryCleanupJob(cx, cleanupJob)) {
AutoEnterOOMUnsafeRegion oomUnsafe;
oomUnsafe.crash("queueing FinalizationRegistry cleanup job");
}
data->queuedForCleanup = true;
}
data->compactRecords();
}
static const ClassOps FinalizationRegistryObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
FinalizationRegistryObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
FinalizationRegistryObject::trace
};
const Class FinalizationRegistryObject::class_ = {
"FinalizationRegistry",
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) |
JSCLASS_FOREGROUND_FINALIZE,
&FinalizationRegistryObjectClassOps
};
/* static */ JSObject*
FinalizationRegistryObject::initClass(JSContext* cx, HandleObject obj)
{
return ::InitFinalizationRegistryClass(cx, obj, true);
}
JSObject*
js::InitFinalizationRegistryClass(JSContext* cx, HandleObject obj)
{
return FinalizationRegistryObject::initClass(cx, obj);
}
JSObject*
js::InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj)
{
return ::InitFinalizationRegistryClass(cx, obj, false);
}
@@ -0,0 +1,51 @@
/* -*- 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_FinalizationRegistryObject_h
#define builtin_FinalizationRegistryObject_h
#include "gc/Barrier.h"
#include "vm/NativeObject.h"
namespace js {
class FinalizationRegistryObject : public NativeObject
{
public:
struct Data;
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static FinalizationRegistryObject* create(JSContext* cx, HandleObject cleanupCallback,
HandleObject proto = nullptr);
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(FreeOp* fop, JSObject* obj);
void traceWeakEdgesForCollectedZones(JSTracer* trc);
[[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);
[[nodiscard]] static bool register_(JSContext* cx, unsigned argc, Value* vp);
[[nodiscard]] static bool unregister(JSContext* cx, unsigned argc, Value* vp);
[[nodiscard]] static bool cleanupJob(JSContext* cx, unsigned argc, Value* vp);
void sweepAfterGC(JSRuntime* rt);
Data* getData() const {
return static_cast<Data*>(getPrivate());
}
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
};
extern JSObject*
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj);
extern JSObject*
InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj);
} // namespace js
#endif /* builtin_FinalizationRegistryObject_h */
+43
View File
@@ -55,6 +55,49 @@ function MapForEach(callbackfn, thisArg = undefined) {
}
}
// ES2024
// Map.groupBy ( items, callbackfn )
function MapGroupBy(items, callbackfn) {
// Step 1.
RequireObjectCoercible(items);
// Step 2.
if (!IsCallable(callbackfn))
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
// Step 3.
var groups = std_Map_create();
// Step 4.
var k = 0;
// Steps 5-8.
for (var value of allowContentIter(items)) {
// Step 6.a.
if (k >= MAX_NUMERIC_INDEX)
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
// Step 6.b.
var key = callContentFunction(callbackfn, undefined, value, k);
// Steps 6.c-d.
var elements;
if (callFunction(std_Map_has, groups, key)) {
elements = callFunction(std_Map_get, groups, key);
callFunction(std_Array_push, elements, value);
} else {
elements = [value];
callFunction(std_Map_set, groups, key, elements);
}
// Step 6.e.
k++;
}
// Step 9.
return groups;
}
var iteratorTemp = { mapIterationResultPair : null };
function MapIteratorNext() {
+15 -4
View File
@@ -332,9 +332,15 @@ const JSPropertySpec MapObject::staticProperties[] = {
JS_PS_END
};
const JSFunctionSpec MapObject::staticMethods[] = {
JS_SELF_HOSTED_FN("groupBy", "MapGroupBy", 2, 0),
JS_FS_END
};
static JSObject*
InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
const JSPropertySpec* properties, const JSFunctionSpec* methods,
const JSFunctionSpec* staticMethods,
const JSPropertySpec* staticProperties)
{
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
@@ -343,8 +349,13 @@ InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSPro
Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
if (!ctor ||
!JS_DefineProperties(cx, ctor, staticProperties) ||
!LinkConstructorAndPrototype(cx, ctor, proto) ||
!JS_DefineProperties(cx, ctor, staticProperties))
{
return nullptr;
}
if (staticMethods && !JS_DefineFunctions(cx, ctor, staticMethods))
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
{
@@ -359,7 +370,7 @@ MapObject::initClass(JSContext* cx, JSObject* obj)
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
staticProperties));
staticMethods, staticProperties));
if (proto) {
// Define the "entries" method.
JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
@@ -1084,7 +1095,7 @@ SetObject::initClass(JSContext* cx, JSObject* obj)
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
staticProperties));
nullptr, staticProperties));
if (proto) {
// Define the "values" method.
JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
+3 -2
View File
@@ -110,7 +110,9 @@ class MapObject : public NativeObject {
static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
JS::MutableHandle<GCVector<JS::Value>> entries);
static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
static MapObject* create(JSContext* cx, HandleObject proto = nullptr);
// Publicly exposed Map calls for JSAPI access (webidl maplike/setlike
@@ -137,6 +139,7 @@ class MapObject : public NativeObject {
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
static const JSFunctionSpec staticMethods[];
static const JSPropertySpec staticProperties[];
ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); }
static ValueMap& extract(HandleObject o);
@@ -153,10 +156,8 @@ class MapObject : public NativeObject {
static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool get_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool set_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool keys_impl(JSContext* cx, const CallArgs& args);
+7 -3
View File
@@ -240,12 +240,16 @@ function ObjectGroupBy(items, callbackfn) {
// Steps 5-8.
for (var value of allowContentIter(items)) {
// Step 6.a.
var key = callContentFunction(callbackfn, undefined, value, k);
if (k >= MAX_NUMERIC_INDEX)
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
// Step 6.b.
var key = callContentFunction(callbackfn, undefined, value, k);
// Step 6.c.
key = ToPropertyKey(key);
// Steps 6.c-d.
// Steps 6.d-e.
var elements = groups[key];
if (elements === undefined) {
_DefineDataProperty(groups, key, [value]);
@@ -253,7 +257,7 @@ function ObjectGroupBy(items, callbackfn) {
callFunction(std_Array_push, elements, value);
}
// Step 6.e.
// Step 6.f.
k++;
}
+43
View File
@@ -3722,6 +3722,48 @@ Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
return true;
}
// ES2024
// Promise.withResolvers ( )
static bool
Promise_static_withResolvers(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!args.thisv().isObject()) {
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, args.thisv(), nullptr);
return false;
}
RootedObject C(cx, &args.thisv().toObject());
// Step 2.
Rooted<PromiseCapability> capability(cx);
if (!NewPromiseCapability(cx, C, &capability, false))
return false;
// Step 3.
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!obj)
return false;
// Steps 4-7.
RootedValue promise(cx, ObjectValue(*capability.promise()));
if (!::JS_DefineProperty(cx, obj, "promise", promise, JSPROP_ENUMERATE))
return false;
RootedValue resolve(cx, ObjectValue(*capability.resolve()));
if (!::JS_DefineProperty(cx, obj, "resolve", resolve, JSPROP_ENUMERATE))
return false;
RootedValue reject(cx, ObjectValue(*capability.reject()));
if (!::JS_DefineProperty(cx, obj, "reject", reject, JSPROP_ENUMERATE))
return false;
// Step 8.
args.rval().setObject(*obj);
return true;
}
/**
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
*/
@@ -5251,6 +5293,7 @@ static const JSFunctionSpec promise_static_methods[] = {
JS_FN("race", Promise_static_race, 1, 0),
JS_FN("reject", Promise_reject, 1, 0),
JS_FN("resolve", Promise_static_resolve, 1, 0),
JS_FN("withResolvers", Promise_static_withResolvers, 0, 0),
JS_FS_END
};
+76
View File
@@ -654,6 +654,82 @@ function String_repeat(count) {
return T;
}
// ES2024
// String.prototype.isWellFormed ( )
function String_isWellFormed() {
// Steps 1-2.
RequireObjectCoercible(this);
var S = ToString(this);
// Step 3.
var length = S.length;
for (var k = 0; k < length; k++) {
var c = callFunction(std_String_charCodeAt, S, k);
if (c >= 0xD800 && c <= 0xDBFF) {
if (k + 1 >= length)
return false;
var d = callFunction(std_String_charCodeAt, S, k + 1);
if (d < 0xDC00 || d > 0xDFFF)
return false;
k++;
} else if (c >= 0xDC00 && c <= 0xDFFF) {
return false;
}
}
// Step 4.
return true;
}
// ES2024
// String.prototype.toWellFormed ( )
function String_toWellFormed() {
// Steps 1-2.
RequireObjectCoercible(this);
var S = ToString(this);
// Step 3.
var length = S.length;
var result = "";
var copied = 0;
for (var k = 0; k < length; k++) {
var c = callFunction(std_String_charCodeAt, S, k);
var isUnpairedSurrogate = false;
if (c >= 0xD800 && c <= 0xDBFF) {
if (k + 1 < length) {
var d = callFunction(std_String_charCodeAt, S, k + 1);
if (d >= 0xDC00 && d <= 0xDFFF) {
k++;
continue;
}
}
isUnpairedSurrogate = true;
} else if (c >= 0xDC00 && c <= 0xDFFF) {
isUnpairedSurrogate = true;
}
if (isUnpairedSurrogate) {
if (copied < k)
result += callFunction(String_substring, S, copied, k);
result += "\uFFFD";
copied = k + 1;
}
}
if (copied === 0)
return S;
if (copied < length)
result += callFunction(String_substring, S, copied, length);
// Step 4.
return result;
}
// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
function String_iterator() {
RequireObjectCoercible(this);
+26 -2
View File
@@ -41,10 +41,21 @@ function TypedArrayContentTypeIsBigIntMethod() {
return IsBigInt64TypedArray(this) || IsBigUint64TypedArray(this);
}
function ThrowIfTypedArrayOutOfBounds(tarray) {
if (TypedArrayIsOutOfBounds(tarray))
ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
}
function ThrowIfPossiblyWrappedTypedArrayOutOfBounds(tarray) {
if (PossiblyWrappedTypedArrayIsOutOfBounds(tarray))
ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
}
function GetAttachedArrayBuffer(tarray) {
var buffer = ViewedArrayBufferIfReified(tarray);
if (IsDetachedBuffer(buffer))
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
ThrowIfTypedArrayOutOfBounds(tarray);
return buffer;
}
@@ -67,6 +78,7 @@ function IsTypedArrayEnsuringArrayBuffer(arg) {
if (IsObject(arg) && IsPossiblyWrappedTypedArray(arg)) {
if (PossiblyWrappedTypedArrayHasDetachedBuffer(arg))
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(arg);
return false;
}
@@ -89,6 +101,7 @@ function ValidateTypedArray(obj, error) {
if (IsPossiblyWrappedTypedArray(obj)) {
if (PossiblyWrappedTypedArrayHasDetachedBuffer(obj))
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(obj);
return false;
}
}
@@ -1130,12 +1143,18 @@ function TypedArraySet(overloaded, offset = 0) {
// Steps 9-10.
var targetBuffer = GetAttachedArrayBuffer(target);
ThrowIfTypedArrayOutOfBounds(target);
// Step 11.
var targetLength = TypedArrayLength(target);
// Steps 12 et seq.
if (IsPossiblyWrappedTypedArray(overloaded))
if (IsPossiblyWrappedTypedArray(overloaded)) {
if (PossiblyWrappedTypedArrayHasDetachedBuffer(overloaded))
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(overloaded);
return SetFromTypedArray(target, overloaded, targetOffset, targetLength);
}
return SetFromNonTypedArray(target, overloaded, targetOffset, targetLength, targetBuffer);
}
@@ -1472,6 +1491,8 @@ function TypedArraySubarray(begin, end) {
"TypedArraySubarray");
}
GetAttachedArrayBuffer(obj);
// Steps 4-6.
var buffer = TypedArrayBuffer(obj);
var srcLength = TypedArrayLength(obj);
@@ -1952,7 +1973,10 @@ function ArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
// Steps 19-21.
ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
var currentLen = ArrayBufferByteLength(O);
var copyLen = first >= currentLen ? 0 : std_Math_min(newLen, currentLen - first);
if (copyLen > 0)
ArrayBufferCopyData(new_, 0, O, first | 0, copyLen | 0, isWrapped);
// Step 22.
return new_;
+171 -52
View File
@@ -8,6 +8,7 @@
#include "jsapi.h"
#include "jscntxt.h"
#include "builtin/WeakRefObject.h"
#include "vm/SelfHosting.h"
#include "vm/Interpreter-inl.h"
@@ -21,21 +22,80 @@ IsWeakMap(HandleValue v)
return v.isObject() && v.toObject().is<WeakMapObject>();
}
struct WeakMapObject::Data
{
ObjectValueMap objectMap;
SymbolValueMap symbolMap;
Data(JSContext* cx, JSObject* owner)
: objectMap(cx, owner),
symbolMap(cx, owner)
{}
bool init() {
return objectMap.init() && symbolMap.init();
}
};
ObjectValueMap*
WeakMapObject::getMap()
{
Data* data = getData();
return data ? &data->objectMap : nullptr;
}
SymbolValueMap*
WeakMapObject::getSymbolMap()
{
Data* data = getData();
return data ? &data->symbolMap : nullptr;
}
static bool
EnsureWeakMapData(JSContext* cx, Handle<WeakMapObject*> mapObj)
{
if (mapObj->getData())
return true;
auto data = cx->make_unique<WeakMapObject::Data>(cx, mapObj.get());
if (!data)
return false;
if (!data->init()) {
JS_ReportOutOfMemory(cx);
return false;
}
mapObj->setPrivate(data.release());
return true;
}
MOZ_ALWAYS_INLINE bool
WeakMap_has_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
if (!args.get(0).isObject()) {
if (!CanBeHeldWeakly(args.get(0))) {
args.rval().setBoolean(false);
return true;
}
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
JSObject* key = &args[0].toObject();
if (map->has(key)) {
args.rval().setBoolean(true);
return true;
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
if (args.get(0).isObject()) {
if (ObjectValueMap* map = weakMap.getMap()) {
JSObject* key = &args[0].toObject();
if (map->has(key)) {
args.rval().setBoolean(true);
return true;
}
}
} else {
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
JS::Symbol* key = args[0].toSymbol();
if (map->has(key)) {
args.rval().setBoolean(true);
return true;
}
}
}
@@ -55,16 +115,27 @@ WeakMap_get_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
if (!args.get(0).isObject()) {
if (!CanBeHeldWeakly(args.get(0))) {
args.rval().setUndefined();
return true;
}
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
JSObject* key = &args[0].toObject();
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
args.rval().set(ptr->value());
return true;
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
if (args.get(0).isObject()) {
if (ObjectValueMap* map = weakMap.getMap()) {
JSObject* key = &args[0].toObject();
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
args.rval().set(ptr->value());
return true;
}
}
} else {
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
JS::Symbol* key = args[0].toSymbol();
if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
args.rval().set(ptr->value());
return true;
}
}
}
@@ -84,17 +155,29 @@ WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
if (!args.get(0).isObject()) {
if (!CanBeHeldWeakly(args.get(0))) {
args.rval().setBoolean(false);
return true;
}
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
JSObject* key = &args[0].toObject();
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
map->remove(ptr);
args.rval().setBoolean(true);
return true;
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
if (args.get(0).isObject()) {
if (ObjectValueMap* map = weakMap.getMap()) {
JSObject* key = &args[0].toObject();
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
map->remove(ptr);
args.rval().setBoolean(true);
return true;
}
}
} else {
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
JS::Symbol* key = args[0].toSymbol();
if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
map->remove(ptr);
args.rval().setBoolean(true);
return true;
}
}
}
@@ -126,22 +209,13 @@ TryPreserveReflector(JSContext* cx, HandleObject obj)
return true;
}
static MOZ_ALWAYS_INLINE bool
SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
HandleObject key, HandleValue value)
static bool
SetWeakMapObjectEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
HandleObject key, HandleValue value)
{
if (!EnsureWeakMapData(cx, mapObj))
return false;
ObjectValueMap* map = mapObj->getMap();
if (!map) {
auto newMap = cx->make_unique<ObjectValueMap>(cx, mapObj.get());
if (!newMap)
return false;
if (!newMap->init()) {
JS_ReportOutOfMemory(cx);
return false;
}
map = newMap.release();
mapObj->setPrivate(map);
}
// Preserve wrapped native keys to prevent wrapper optimization.
if (!TryPreserveReflector(cx, key))
@@ -162,13 +236,28 @@ SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
return true;
}
MOZ_ALWAYS_INLINE bool
WeakMap_set_impl(JSContext* cx, const CallArgs& args)
static bool
SetWeakMapSymbolEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
Handle<JS::Symbol*> key, HandleValue value)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
if (!EnsureWeakMapData(cx, mapObj))
return false;
SymbolValueMap* map = mapObj->getSymbolMap();
if (!args.get(0).isObject()) {
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
if (!map->put(key, value)) {
JS_ReportOutOfMemory(cx);
return false;
}
return true;
}
static MOZ_ALWAYS_INLINE bool
SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
HandleValue key, HandleValue value)
{
if (!CanBeHeldWeakly(key)) {
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, key, nullptr);
if (!bytes)
return false;
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
@@ -176,11 +265,24 @@ WeakMap_set_impl(JSContext* cx, const CallArgs& args)
return false;
}
RootedObject key(cx, &args[0].toObject());
if (key.isObject()) {
RootedObject objectKey(cx, &key.toObject());
return SetWeakMapObjectEntryInternal(cx, mapObj, objectKey, value);
}
Rooted<JS::Symbol*> symbolKey(cx, key.toSymbol());
return SetWeakMapSymbolEntryInternal(cx, mapObj, symbolKey, value);
}
MOZ_ALWAYS_INLINE bool
WeakMap_set_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
if (!SetWeakMapEntryInternal(cx, map, key, args.get(1)))
if (!SetWeakMapEntryInternal(cx, map, args.get(0), args.get(1)))
return false;
args.rval().set(args.thisv());
return true;
@@ -193,6 +295,13 @@ js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
}
bool
js::SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val)
{
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
}
JS_FRIEND_API(bool)
JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
{
@@ -205,11 +314,11 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
RootedObject arr(cx, NewDenseEmptyArray(cx));
if (!arr)
return false;
ObjectValueMap* map = obj->as<WeakMapObject>().getMap();
if (map) {
WeakMapObject::Data* data = obj->as<WeakMapObject>().getData();
if (data) {
// Prevent GC from mutating the weakmap while iterating.
AutoSuppressGC suppress(cx);
for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
for (ObjectValueMap::Base::Range r = data->objectMap.all(); !r.empty(); r.popFront()) {
JS::ExposeObjectToActiveJS(r.front().key());
RootedObject key(cx, r.front().key());
if (!cx->compartment()->wrap(cx, &key))
@@ -217,6 +326,14 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
return false;
}
for (SymbolValueMap::Base::Range r = data->symbolMap.all(); !r.empty(); r.popFront()) {
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(r.front().key().get()));
RootedValue key(cx, SymbolValue(r.front().key()));
if (!cx->compartment()->wrap(cx, &key))
return false;
if (!NewbornArrayPush(cx, arr, key))
return false;
}
}
ret.set(arr);
return true;
@@ -225,21 +342,23 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
static void
WeakMap_mark(JSTracer* trc, JSObject* obj)
{
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap())
map->trace(trc);
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
data->objectMap.trace(trc);
data->symbolMap.trace(trc);
}
}
static void
WeakMap_finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->maybeOffMainThread());
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap()) {
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
#ifdef DEBUG
map->~ObjectValueMap();
memset(static_cast<void*>(map), 0xdc, sizeof(*map));
fop->free_(map);
data->~Data();
memset(static_cast<void*>(data), 0xdc, sizeof(*data));
fop->free_(data);
#else
fop->delete_(map);
fop->delete_(data);
#endif
}
}
@@ -282,7 +401,8 @@ JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
CHECK_REQUEST(cx);
assertSameCompartment(cx, key, val);
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
RootedValue keyValue(cx, ObjectValue(*key));
return SetWeakMapEntryInternal(cx, rootedMap, keyValue, val);
}
static bool
@@ -386,4 +506,3 @@ js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
{
return InitWeakMapClass(cx, obj, false);
}
+5 -1
View File
@@ -16,7 +16,11 @@ class WeakMapObject : public NativeObject
public:
static const Class class_;
ObjectValueMap* getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
struct Data;
Data* getData() { return static_cast<Data*>(getPrivate()); }
ObjectValueMap* getMap();
SymbolValueMap* getSymbolMap();
};
} // namespace js
+94 -28
View File
@@ -11,7 +11,9 @@
#include "gc/Nursery.h"
#include "gc/Tracer.h"
#include "vm/GlobalObject.h"
#include "vm/Symbol.h"
#include "jswrapper.h"
#include "jsobjinlines.h"
#include "vm/Interpreter-inl.h"
@@ -31,17 +33,64 @@ IsWeakRef(HandleValue v)
return v.isObject() && v.toObject().is<WeakRefObject>();
}
static bool
GetPrototypeFromWeakRefConstructor(JSContext* cx, HandleObject newTarget,
MutableHandleObject proto)
{
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
return false;
if (proto)
return true;
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
if (!realmObject)
return false;
{
JSAutoCompartment ac(cx, realmObject);
Rooted<GlobalObject*> global(cx, &realmObject->global());
if (!GlobalObject::ensureConstructor(cx, global, JSProto_WeakRef))
return false;
proto.set(&global->getPrototype(JSProto_WeakRef).toObject());
}
return cx->compartment()->wrap(cx, proto);
}
static bool
WeakRef_deref_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakRef(args.thisv()));
WeakRefObject::Referent* data = GetReferent(&args.thisv().toObject());
JSObject* target = data ? data->target.get() : nullptr;
if (target)
args.rval().setObject(*target);
else
if (!data) {
args.rval().setUndefined();
return true;
}
if (data->isObject()) {
JSObject* target = data->objectTarget.get();
if (target) {
RootedValue kept(cx, ObjectValue(*target));
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
return false;
args.rval().set(kept);
} else {
args.rval().setUndefined();
}
} else {
MOZ_ASSERT(data->isSymbol());
JS::Symbol* target = data->symbolTarget.get();
if (target) {
RootedValue kept(cx, SymbolValue(target));
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
return false;
args.rval().set(kept);
} else {
args.rval().setUndefined();
}
}
return true;
}
@@ -83,13 +132,18 @@ InitWeakRefClass(JSContext* cx, HandleObject obj, bool defineMembers)
}
/* static */ WeakRefObject*
WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /* = nullptr */)
WeakRefObject::create(JSContext* cx, HandleValue target, HandleObject proto /* = nullptr */)
{
Rooted<WeakRefObject*> obj(cx, NewObjectWithClassProto<WeakRefObject>(cx, proto));
if (!obj)
return nullptr;
Referent* data = cx->new_<Referent>(target, cx->options().weakRefs());
Referent* data;
if (target.isObject())
data = cx->new_<Referent>(&target.toObject());
else
data = cx->new_<Referent>(target.toSymbol());
if (!data)
return nullptr;
@@ -97,6 +151,18 @@ WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /*
return obj;
}
bool
js::CanBeHeldWeakly(HandleValue target)
{
if (target.isObject())
return true;
if (target.isSymbol())
return target.toSymbol()->code() != JS::SymbolCode::InSymbolRegistry;
return false;
}
/* static */ bool
WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
@@ -105,28 +171,25 @@ WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
if (!ThrowIfNotConstructing(cx, args, "WeakRef"))
return false;
if (!args.get(0).isObject()) {
UniqueChars bytes =
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
if (!bytes)
return false;
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
bytes.get());
if (!CanBeHeldWeakly(args.get(0))) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_WEAKREF_TARGET);
return false;
}
RootedObject target(cx, &args[0].toObject());
RootedValue target(cx, args[0]);
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
if (!GetPrototypeFromWeakRefConstructor(cx, newTarget, &proto))
return false;
Rooted<WeakRefObject*> obj(cx, WeakRefObject::create(cx, target, proto));
if (!obj)
return false;
if (!cx->runtime()->addWeakRefKeptObject(cx, target))
return false;
args.rval().setObject(*obj);
return true;
}
@@ -142,19 +205,23 @@ WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp)
WeakRefObject::trace(JSTracer* trc, JSObject* obj)
{
if (Referent* data = GetReferent(obj)) {
JSObject* target = data->target.unbarrieredGet();
if (!target)
return;
if (data->isObject()) {
JSObject* target = data->objectTarget.unbarrieredGet();
if (!target)
return;
// When pref-disabled, keep referent alive via strong trace so deref()
// stays usable as a stub without touching GC internals.
if (!data->enabled) {
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef stub referent");
} else if (IsInsideNursery(target)) {
// Weak edges must be tenured; trace strongly while referent is in the nursery.
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef nursery referent");
if (IsInsideNursery(target)) {
// Weak edges must be tenured; trace strongly while referent is in the nursery.
TraceManuallyBarrieredEdge(trc, data->objectTarget.unsafeGet(),
"WeakRef nursery referent");
} else {
TraceWeakEdge(trc, &data->objectTarget, "WeakRef referent");
}
} else {
TraceWeakEdge(trc, &data->target, "WeakRef referent");
MOZ_ASSERT(data->isSymbol());
JS::Symbol* target = data->symbolTarget.unbarrieredGet();
if (target)
TraceWeakEdge(trc, &data->symbolTarget, "WeakRef symbol referent");
}
}
}
@@ -162,7 +229,6 @@ WeakRefObject::trace(JSTracer* trc, JSObject* obj)
/* static */ void
WeakRefObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(!fop->maybeOffMainThread());
if (Referent* data = GetReferent(obj))
fop->delete_(data);
}
+21 -6
View File
@@ -11,20 +11,34 @@
namespace js {
bool CanBeHeldWeakly(HandleValue target);
class WeakRefObject : public NativeObject
{
public:
struct Referent {
explicit Referent(JSObject* obj, bool enabled)
: target(obj), enabled(enabled) {}
WeakRef<JSObject*> target;
bool enabled;
enum class Kind {
Object,
Symbol
};
explicit Referent(JSObject* obj)
: kind(Kind::Object), objectTarget(obj), symbolTarget(nullptr) {}
explicit Referent(JS::Symbol* sym)
: kind(Kind::Symbol), objectTarget(nullptr), symbolTarget(sym) {}
bool isObject() const { return kind == Kind::Object; }
bool isSymbol() const { return kind == Kind::Symbol; }
Kind kind;
WeakRef<JSObject*> objectTarget;
WeakRef<JS::Symbol*> symbolTarget;
};
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static WeakRefObject* create(JSContext* cx, HandleObject target, HandleObject proto = nullptr);
static WeakRefObject* create(JSContext* cx, HandleValue target, HandleObject proto = nullptr);
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(FreeOp* fop, JSObject* obj);
@@ -37,7 +51,8 @@ class WeakRefObject : public NativeObject
WeakRef<JSObject*>& target() {
MOZ_ASSERT(getData());
return getData()->target;
MOZ_ASSERT(getData()->isObject());
return getData()->objectTarget;
}
static const JSPropertySpec properties[];
+3 -3
View File
@@ -32,7 +32,7 @@ function WeakSet_add(value) {
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
// Step 5.
if (!IsObject(value))
if (!CanBeHeldWeakly(value))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value));
// Steps 7-8.
@@ -55,7 +55,7 @@ function WeakSet_delete(value) {
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "delete", typeof S);
// Step 5.
if (!IsObject(value))
if (!CanBeHeldWeakly(value))
return false;
// Steps 7-8.
@@ -75,7 +75,7 @@ function WeakSet_has(value) {
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "has", typeof S);
// Step 6.
if (!IsObject(value))
if (!CanBeHeldWeakly(value))
return false;
// Steps 7-8.
+3 -4
View File
@@ -12,6 +12,7 @@
#include "builtin/MapObject.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/WeakMapObject.h"
#include "builtin/WeakRefObject.h"
#include "vm/GlobalObject.h"
#include "vm/SelfHosting.h"
@@ -108,7 +109,6 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
if (optimized) {
RootedValue keyVal(cx);
RootedObject keyObject(cx);
RootedValue placeholder(cx, BooleanValue(true));
RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
RootedArrayObject array(cx, &iterable.toObject().as<ArrayObject>());
@@ -116,7 +116,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
keyVal.set(array->getDenseElement(index));
MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
if (keyVal.isPrimitive()) {
if (!CanBeHeldWeakly(keyVal)) {
UniqueChars bytes =
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr);
if (!bytes)
@@ -126,8 +126,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
return false;
}
keyObject = &keyVal.toObject();
if (!SetWeakMapEntry(cx, map, keyObject, placeholder))
if (!SetWeakMapEntryValue(cx, map, keyVal, placeholder))
return false;
}
} else {
+19 -3
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() {
@@ -939,6 +947,8 @@ class GCRuntime
IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
template <class ZoneIterT> void traceFinalizationRegistryWeakRefs();
void traceFinalizationRegistryWeakRefsInCurrentGroup();
template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
void markBufferedGrayRoots(JS::Zone* zone);
void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
@@ -951,6 +961,7 @@ class GCRuntime
void getNextZoneGroup();
void endMarkingZoneGroup();
void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
void sweepFinalizationRegistries();
bool shouldReleaseObservedTypes();
void endSweepingZoneGroup();
IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
@@ -966,6 +977,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();
@@ -976,10 +989,10 @@ class GCRuntime
void endCompactPhase(JS::gcreason::Reason reason);
void sweepTypesAfterCompacting(Zone* zone);
void sweepZoneAfterCompacting(Zone* zone);
MOZ_MUST_USE bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
Arena*& relocatedListOut, SliceBudget& sliceBudget);
[[nodiscard]] bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
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 +1032,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
+5
View File
@@ -12,6 +12,7 @@
#include "jscntxt.h"
#include "ds/SplayTree.h"
#include "gc/Barrier.h"
#include "gc/FindSCCs.h"
#include "gc/GCRuntime.h"
#include "js/GCHashTable.h"
@@ -325,6 +326,10 @@ struct Zone : public JS::shadow::Zone,
using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
WeakEdges gcWeakRefs;
// FinalizationRegistry objects with weakly-held target cells in this zone.
using FinalizationRegistryVector = js::Vector<js::WeakRef<JSObject*>, 0, js::SystemAllocPolicy>;
FinalizationRegistryVector finalizationRegistries;
// List of non-ephemeron weak containers to sweep during beginSweepingZoneGroup.
mozilla::LinkedList<WeakCache<void*>> weakCaches_;
void registerWeakCache(WeakCache<void*>* cachep) {
+13
View File
@@ -1464,6 +1464,12 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
res.isNumber() &&
!TypedArrayGetElemStubExists(stub, obj))
{
if (obj->is<TypedArrayObject>() &&
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
{
return true;
}
if (!cx->runtime()->jitSupportsFloatingPoint &&
(TypedThingRequiresFloatingPoint(obj) || rhs.isDouble()))
{
@@ -2154,6 +2160,8 @@ ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
Register obj = masm.extractObject(R0, ExtractTemp0);
masm.loadPtr(Address(ICStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg);
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
if (layout_ == Layout_TypedArray)
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
// Ensure the index is an integer.
if (cx->runtime()->jitSupportsFloatingPoint) {
@@ -2625,6 +2633,9 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
bool expectOutOfBounds;
double idx = index.toNumber();
if (obj->is<TypedArrayObject>()) {
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
return true;
expectOutOfBounds = (idx < 0 || idx >= double(obj->as<TypedArrayObject>().length()));
} else {
// Typed objects throw on out of bounds accesses. Don't attach
@@ -3215,6 +3226,8 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
Register obj = masm.extractObject(R0, ExtractTemp0);
masm.loadPtr(Address(ICStubReg, ICSetElem_TypedArray::offsetOfShape()), scratchReg);
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
if (layout_ == Layout_TypedArray)
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
// Ensure the index is an integer.
if (cx->runtime()->jitSupportsFloatingPoint) {
+9 -1
View File
@@ -8889,9 +8889,17 @@ CodeGenerator::branchIfNotEmptyObjectElements(Register obj, Label* target)
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElements),
&emptyObj);
masm.branchPtr(Assembler::NotEqual,
masm.branchPtr(Assembler::Equal,
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElementsShared),
&emptyObj);
masm.branchPtr(Assembler::Equal,
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElementsResizableOrGrowable),
&emptyObj);
masm.branchPtr(Assembler::NotEqual,
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElementsSharedResizableOrGrowable),
target);
masm.bind(&emptyObj);
}
+1 -2
View File
@@ -535,8 +535,7 @@ class CodeGenerator final : public CodeGeneratorSpecific
Label* ifDoesntEmulateUndefined,
Register scratch, OutOfLineTestObject* ool);
// Branch to target unless obj has an emptyObjectElements or emptyObjectElementsShared
// elements pointer.
// Branch to target unless obj has one of the empty elements pointers.
void branchIfNotEmptyObjectElements(Register obj, Label* target);
void emitStoreElementTyped(const LAllocation* value, MIRType valueType, MIRType elementType,
+17
View File
@@ -1245,6 +1245,7 @@ GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAtta
masm.branchPtr(Assembler::AboveOrEqual, tmpReg,
ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]),
failures);
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, failures);
// Load length.
masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output);
@@ -1644,6 +1645,9 @@ GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript
if (!JSID_IS_ATOM(id, cx->names().length))
return true;
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
return true;
if (hasTypedArrayLengthStub(obj))
return true;
@@ -4038,6 +4042,12 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i
if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
return false;
if (obj->is<TypedArrayObject>() &&
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
{
return false;
}
MOZ_ASSERT(idval.isInt32() || idval.isString());
// Don't emit a stub if the access is out of bounds. We make to make
@@ -4092,6 +4102,9 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
// Decide to what type index the stub should be optimized
Register tmpReg = output.scratchReg().gpr();
MOZ_ASSERT(tmpReg != InvalidReg);
if (array->is<TypedArrayObject>())
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, &failures);
Register indexReg = tmpReg;
if (idval.isString()) {
MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);
@@ -4575,6 +4588,7 @@ GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::Stub
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
GuardResizableOrGrowableTypedArray(masm, object, temp, &failures);
// Ensure the index is an int32.
Register indexReg;
@@ -4661,6 +4675,9 @@ SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScrip
if (!IsTypedArrayElementSetInlineable(obj, idval, val))
return true;
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+13 -59
View File
@@ -365,13 +365,9 @@ IonBuilder::inlineNativeGetter(CallInfo& callInfo, JSFunction* target)
// Try to optimize typed array lengths.
if (TypedArrayObject::isOriginalLengthGetter(native)) {
Scalar::Type type = thisTypes->getTypedArrayType(constraints());
if (type == Scalar::MaxTypedArrayViewType)
return InliningStatus_NotInlined;
MInstruction* length = addTypedArrayLength(thisArg);
current->push(length);
return InliningStatus_Inlined;
// RAB/GSAB views can have dynamic length or temporarily become
// out-of-bounds. Let the property IC/VM path handle the getter.
return InliningStatus_NotInlined;
}
// Try to optimize RegExp getters.
@@ -2480,21 +2476,10 @@ IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def)
IonBuilder::InliningStatus
IonBuilder::inlinePossiblyWrappedTypedArrayLength(CallInfo& callInfo)
{
MOZ_ASSERT(!callInfo.constructing());
MOZ_ASSERT(callInfo.argc() == 1);
if (callInfo.getArg(0)->type() != MIRType::Object)
return InliningStatus_NotInlined;
if (getInlineReturnType() != MIRType::Int32)
return InliningStatus_NotInlined;
(void) callInfo;
if (!IsTypedArrayObject(constraints(), callInfo.getArg(0)))
return InliningStatus_NotInlined;
MInstruction* length = addTypedArrayLength(callInfo.getArg(0));
current->push(length);
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
// RAB/GSAB views require dynamic length semantics.
return InliningStatus_NotInlined;
}
IonBuilder::InliningStatus
@@ -3251,45 +3236,14 @@ bool
IonBuilder::atomicsMeetsPreconditions(CallInfo& callInfo, Scalar::Type* arrayType,
bool* requiresTagCheck, AtomicCheckResult checkResult)
{
if (!JitSupportsAtomics())
return false;
(void) callInfo;
(void) arrayType;
(void) requiresTagCheck;
(void) checkResult;
if (callInfo.getArg(0)->type() != MIRType::Object)
return false;
if (callInfo.getArg(1)->type() != MIRType::Int32)
return false;
// Ensure that the first argument is a TypedArray that maps shared
// memory.
//
// Then check both that the element type is something we can
// optimize and that the return type is suitable for that element
// type.
TemporaryTypeSet* arg0Types = callInfo.getArg(0)->resultTypeSet();
if (!arg0Types)
return false;
TemporaryTypeSet::TypedArraySharedness sharedness;
*arrayType = arg0Types->getTypedArrayType(constraints(), &sharedness);
*requiresTagCheck = sharedness != TemporaryTypeSet::KnownShared;
switch (*arrayType) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Int32;
case Scalar::Uint32:
// Bug 1077305: it would be attractive to allow inlining even
// if the inline return type is Int32, which it will frequently
// be.
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Double;
default:
// Excludes floating types and Uint8Clamped.
return false;
}
// Atomics bounds checks through addTypedArrayLengthAndData assume stable
// SharedArrayBuffer lengths. Avoid this path now that growable SAB exists.
return false;
}
void
+8 -18
View File
@@ -5505,25 +5505,15 @@ jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id,
Scalar::Type* arrayType)
{
if (obj->mightBeType(MIRType::String))
return false;
(void) constraints;
(void) obj;
(void) id;
(void) arrayType;
if (id->type() != MIRType::Int32 && id->type() != MIRType::Double)
return false;
TemporaryTypeSet* types = obj->resultTypeSet();
if (!types)
return false;
*arrayType = types->getTypedArrayType(constraints);
// FIXME: https://bugzil.la/1536699
if (*arrayType == Scalar::MaxTypedArrayViewType ||
Scalar::isBigIntType(*arrayType)) {
return false;
}
return true;
// Resizable ArrayBuffer and growable SharedArrayBuffer views need dynamic
// length/out-of-bounds handling. This older MIR path assumes stable
// typed-array length/data slots, so keep element accesses on IC/VM paths.
return false;
}
bool
+11
View File
@@ -3608,6 +3608,17 @@ CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Labe
masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure);
}
void
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
Label* failure)
{
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
masm.branchTest32(Assembler::NonZero,
Address(scratch, ObjectElements::offsetOfFlags()),
Imm32(ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER),
failure);
}
void
LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
{
+4
View File
@@ -2302,6 +2302,10 @@ CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, Register
void
CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure);
void
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
Label* failure);
MOZ_MUST_USE bool
DoCallNativeGetter(JSContext* cx, HandleFunction callee, HandleObject obj,
MutableHandleValue result);
+9
View File
@@ -81,6 +81,10 @@ MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty array
MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}")
MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 1, JSEXN_TYPEERR, "{0} is not a non-null object")
MSG_DEF(JSMSG_NOT_WEAKREF_TARGET, 0, JSEXN_TYPEERR, "WeakRef target must be an object or a non-registered symbol")
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TARGET, 0, JSEXN_TYPEERR, "FinalizationRegistry target must be an object or a non-registered symbol")
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE, 0, JSEXN_TYPEERR, "FinalizationRegistry held value must not be the target")
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN, 0, JSEXN_TYPEERR, "FinalizationRegistry unregister token must be an object or a non-registered symbol")
MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 1, JSEXN_TYPEERR, "can't assign to properties of {0}: not an object")
MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0}: Object is not extensible")
@@ -548,6 +552,8 @@ MSG_DEF(JSMSG_TOO_LONG_ARRAY, 0, JSEXN_TYPEERR, "Too long array")
// Typed array
MSG_DEF(JSMSG_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
MSG_DEF(JSMSG_ARRAYBUFFER_NOT_RESIZABLE, 0, JSEXN_TYPEERR, "ArrayBuffer is not resizable")
MSG_DEF(JSMSG_ARRAYBUFFER_CANNOT_DETACH, 0, JSEXN_TYPEERR, "ArrayBuffer cannot be detached")
MSG_DEF(JSMSG_NON_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected ArrayBuffer, but species constructor returned non-ArrayBuffer")
MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different ArrayBuffer, but species constructor returned same ArrayBuffer")
MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes")
@@ -555,12 +561,15 @@ MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS, 0, JSEXN_RANGEERR, "attempting to construct out-of-bounds TypedArray on ArrayBuffer")
MSG_DEF(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds TypedArray")
MSG_DEF(JSMSG_DATA_VIEW_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds DataView")
MSG_DEF(JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1, JSEXN_TYPEERR, "cannot directly {0} builtin %TypedArray%")
MSG_DEF(JSMSG_NON_TYPED_ARRAY_RETURNED, 0, JSEXN_TYPEERR, "constructor didn't return TypedArray object")
MSG_DEF(JSMSG_SHORT_TYPED_ARRAY_RETURNED, 2, JSEXN_TYPEERR, "expected TypedArray of at least length {0}, but constructor returned TypedArray of length {1}")
// Shared array buffer
MSG_DEF(JSMSG_SHARED_ARRAY_BAD_LENGTH, 0, JSEXN_RANGEERR, "length argument out of range")
MSG_DEF(JSMSG_SHARED_ARRAY_NOT_GROWABLE, 0, JSEXN_TYPEERR, "SharedArrayBuffer is not growable")
MSG_DEF(JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected SharedArrayBuffer, but species constructor returned non-SharedArrayBuffer")
MSG_DEF(JSMSG_SAME_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different SharedArrayBuffer, but species constructor returned same SharedArrayBuffer")
MSG_DEF(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected SharedArrayBuffer with at least {0} bytes, but species constructor returns SharedArrayBuffer with {1} bytes")
+58 -2
View File
@@ -21,6 +21,64 @@ struct MyHeap
BEGIN_TEST(testGCWeakRef)
{
CHECK(cx->options().weakRefs());
cx->options().setWeakRefs(false);
CHECK(cx->options().weakRefs());
JS::RootedValue v(cx);
EXEC("var weakRefTarget = { x: 42 };\n"
"var weakRef = new WeakRef(weakRefTarget);\n"
"weakRefTarget = null;\n");
// The constructor keeps the target alive until the host clears kept
// objects. This must remain true even after callers try the old disabled
// option path above.
JS_GC(cx);
JS_GC(cx);
EVAL("weakRef.deref()", &v);
CHECK(v.isObject());
JS::ClearWeakRefKeptObjects(cx);
v = JS::UndefinedValue();
JS_GC(cx);
JS_GC(cx);
EVAL("weakRef.deref()", &v);
CHECK(v.isUndefined());
EXEC("var weakRefSymbol = Symbol('weak-ref-symbol');\n"
"var symbolRef = new WeakRef(weakRefSymbol);\n"
"if (symbolRef.deref() !== weakRefSymbol)\n"
" throw new Error('WeakRef must accept unique symbols');\n"
"var registeredSymbolRejected = false;\n"
"try {\n"
" new WeakRef(Symbol.for('weak-ref-symbol'));\n"
"} catch (e) {\n"
" registeredSymbolRejected = e instanceof TypeError;\n"
"}\n"
"if (!registeredSymbolRejected)\n"
" throw new Error('WeakRef must reject registered symbols');\n");
EXEC("var keptTarget = { y: 7 };\n"
"var keptRef = new WeakRef(keptTarget);\n");
JS::ClearWeakRefKeptObjects(cx);
EXEC("var keptResult = keptRef.deref();\n"
"if (keptResult !== keptTarget)\n"
" throw new Error('WeakRef deref must return the target');\n"
"keptTarget = null;\n"
"keptResult = null;\n");
JS_GC(cx);
JS_GC(cx);
EVAL("keptRef.deref()", &v);
CHECK(v.isObject());
JS::ClearWeakRefKeptObjects(cx);
v = JS::UndefinedValue();
JS_GC(cx);
JS_GC(cx);
EVAL("keptRef.deref()", &v);
CHECK(v.isUndefined());
// Create an object and add a property to it so that we can read the
// property back later to verify that object internals are not garbage.
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
@@ -38,7 +96,6 @@ BEGIN_TEST(testGCWeakRef)
// references.
CHECK(heap.get().weak.unbarrieredGet() != nullptr);
obj = heap.get().weak;
JS::RootedValue v(cx);
CHECK(JS_GetProperty(cx, obj, "x", &v));
CHECK(v.isInt32());
CHECK(v.toInt32() == 42);
@@ -61,4 +118,3 @@ BEGIN_TEST(testGCWeakRef)
return true;
}
END_TEST(testGCWeakRef)
+39
View File
@@ -627,6 +627,15 @@ JS::InitSelfHostedCode(JSContext* cx)
return true;
}
JS_PUBLIC_API(void)
JS::ClearWeakRefKeptObjects(JSContext* cx)
{
MOZ_ASSERT(cx);
MOZ_ASSERT(!cx->runtime()->isHeapBusy());
cx->runtime()->clearWeakRefKeptObjects();
}
JS_PUBLIC_API(const char*)
JS_GetImplementationVersion(void)
{
@@ -1478,6 +1487,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,
+44 -5
View File
@@ -999,7 +999,7 @@ class JS_PUBLIC_API(ContextOptions) {
strictMode_(false),
extraWarnings_(false),
arrayProtoValues_(true),
weakRefs_(false)
streams_(false)
{
}
@@ -1139,13 +1139,12 @@ class JS_PUBLIC_API(ContextOptions) {
return *this;
}
bool weakRefs() const { return weakRefs_; }
bool weakRefs() const { return true; }
ContextOptions& setWeakRefs(bool flag) {
weakRefs_ = flag;
(void) flag;
return *this;
}
ContextOptions& toggleWeakRefs() {
weakRefs_ = !weakRefs_;
return *this;
}
@@ -1166,7 +1165,6 @@ class JS_PUBLIC_API(ContextOptions) {
bool extraWarnings_ : 1;
bool arrayProtoValues_ : 1;
bool streams_ : 1;
bool weakRefs_ : 1;
};
JS_PUBLIC_API(ContextOptions&)
@@ -1187,6 +1185,13 @@ InitSelfHostedCode(JSContext* cx);
JS_PUBLIC_API(void)
AssertObjectBelongsToCurrentThread(JSObject* obj);
/**
* Clear objects and symbols that WeakRef.prototype.deref kept alive for the
* current synchronous JavaScript execution.
*/
JS_PUBLIC_API(void)
ClearWeakRefKeptObjects(JSContext* cx);
} /* namespace JS */
extern JS_PUBLIC_API(const char*)
@@ -1719,6 +1724,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.
+4 -2
View File
@@ -1748,6 +1748,9 @@ JS_IsFloat64Array(JSObject* obj);
extern JS_FRIEND_API(bool)
JS_GetTypedArraySharedness(JSObject* obj);
extern JS_FRIEND_API(uint32_t)
JS_GetTypedArrayLength(JSObject* obj);
/*
* Test for specific typed array types (ArrayBufferView subtypes) and return
* the unwrapped object if so, else nullptr. Never throws.
@@ -1814,8 +1817,7 @@ inline void \
Get ## Type ## ArrayLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, type** data) \
{ \
MOZ_ASSERT(GetObjectClass(obj) == detail::Type ## ArrayClassPtr); \
const JS::Value& lenSlot = GetReservedSlot(obj, detail::TypedArrayLengthSlot); \
*length = mozilla::AssertedCast<uint32_t>(lenSlot.toInt32()); \
*length = JS_GetTypedArrayLength(obj); \
*isSharedMemory = JS_GetTypedArraySharedness(obj); \
*data = static_cast<type*>(GetObjectPrivate(obj)); \
}
+329 -57
View File
@@ -211,9 +211,11 @@
# include "jswin.h"
#endif
#include "builtin/FinalizationRegistryObject.h"
#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 +1572,7 @@ ArenaLists::prepareForIncrementalGC()
{
purge();
for (auto i : AllAllocKinds()) {
arenaLists[i].moveCursorToEnd();
arenaLists[i].moveCursorToEnd();
}
}
@@ -2117,7 +2119,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 +2209,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 +2226,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 +2274,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 +2377,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 +2689,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 +3038,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 +3182,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 +3746,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 +3819,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 +3835,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 +3927,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
bufferGrayRoots();
markCompartments();
}
return true;
}
@@ -3801,9 +3966,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 +3989,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))
@@ -3865,6 +4030,8 @@ GCRuntime::markWeakReferences(gcstats::Phase phase)
}
MOZ_ASSERT(marker.isDrained());
traceFinalizationRegistryWeakRefs<ZoneIterT>();
marker.leaveWeakMarkingMode();
}
@@ -3874,6 +4041,33 @@ GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase)
markWeakReferences<GCZoneGroupIter>(phase);
}
template <class ZoneIterT>
void
GCRuntime::traceFinalizationRegistryWeakRefs()
{
for (ZoneIterT zone(rt); !zone.done(); zone.next()) {
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
WeakRef<JSObject*>& registry = zone->finalizationRegistries[i];
if (registry.unbarrieredGet())
TraceWeakEdge(&marker, &registry, "FinalizationRegistry registry");
}
}
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
JSObject* registry = zone->finalizationRegistries[i].unbarrieredGet();
if (registry && registry->is<FinalizationRegistryObject>())
registry->as<FinalizationRegistryObject>().traceWeakEdgesForCollectedZones(&marker);
}
}
}
void
GCRuntime::traceFinalizationRegistryWeakRefsInCurrentGroup()
{
traceFinalizationRegistryWeakRefs<GCZoneGroupIter>();
}
template <class ZoneIterT, class CompartmentIterT>
void
GCRuntime::markGrayReferences(gcstats::Phase phase)
@@ -4429,7 +4623,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 +4658,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 +4717,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 +4759,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()) {
@@ -4569,6 +4779,8 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
oomUnsafe.crash("clearing weak keys in beginSweepingZoneGroup()");
}
sweepFinalizationRegistries();
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
@@ -4599,7 +4811,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 +4832,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
c->sweepJitCompartment(&fop);
c->sweepTemplateObjects();
}
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next())
zone->sweepWeakMaps();
@@ -4678,7 +4891,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);
}
@@ -4721,6 +4936,23 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
}
}
void
GCRuntime::sweepFinalizationRegistries()
{
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
for (size_t i = 0; i < zone->finalizationRegistries.length();) {
JSObject* obj = zone->finalizationRegistries[i].unbarrieredGet();
if (!obj || !obj->is<FinalizationRegistryObject>()) {
zone->finalizationRegistries.erase(zone->finalizationRegistries.begin() + i);
continue;
}
obj->as<FinalizationRegistryObject>().sweepAfterGC(rt);
i++;
}
}
}
void
GCRuntime::endSweepingZoneGroup()
{
@@ -5005,7 +5237,7 @@ GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess&
// Reset phase index.
sweepPhaseIndex = 0;
endSweepingZoneGroup();
getNextZoneGroup();
if (!currentZoneGroup)
@@ -5107,6 +5339,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 +5791,7 @@ gc::AbortReason
gc::IsIncrementalGCUnsafe(JSRuntime* rt)
{
MOZ_ASSERT(!rt->mainThread.suppressGC);
if (rt->keepAtoms())
return gc::AbortReason::KeepAtomsSet;
@@ -5579,7 +5812,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 +5820,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
return;
}
if (isTooMuchMalloc()) {
budget.makeUnlimited();
@@ -5827,6 +6060,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 +6074,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 +6087,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 +6117,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 +6133,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
@@ -5899,6 +6144,13 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
} while (repeat);
if (rt->isBeingDestroyed()) {
rt->clearFinalizationRegistryCleanupJobs();
} else if (!rt->drainFinalizationRegistryCleanupJobs(rt->contextFromMainThread())) {
AutoEnterOOMUnsafeRegion oomUnsafe;
oomUnsafe.crash("draining FinalizationRegistry cleanup jobs");
}
if (reason == JS::gcreason::COMPARTMENT_REVIVED)
maybeDoCycleCollection();
}
@@ -5912,8 +6164,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 +6261,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
@@ -97,6 +97,7 @@
IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \
IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \
IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
real(FinalizationRegistry, InitFinalizationRegistryClass, OCLASP(FinalizationRegistry)) \
real(Reflect, InitReflect, nullptr) \
real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \
real(TypedArray, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
+2
View File
@@ -3305,6 +3305,8 @@ static const JSFunctionSpec string_methods[] = {
JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
JS_SELF_HOSTED_FN("isWellFormed", "String_isWellFormed", 0,0),
JS_SELF_HOSTED_FN("toWellFormed", "String_toWellFormed", 0,0),
JS_FN("normalize", str_normalize, 0,0),
/* Perl-ish methods (search is actually Python-esque). */
+38
View File
@@ -16,11 +16,25 @@
#include "gc/Marking.h"
#include "gc/StoreBuffer.h"
#include "js/HashTable.h"
#include "vm/Symbol.h"
namespace js {
class WeakMapBase;
template <>
struct MovableCellHasher<JS::Symbol*>
{
using Key = JS::Symbol*;
using Lookup = JS::Symbol*;
static bool hasHash(const Lookup& l) { return true; }
static bool ensureHash(const Lookup& l) { return true; }
static HashNumber hash(const Lookup& l) { return l->hash(); }
static bool match(const Key& k, const Lookup& l) { return k == l; }
static void rekey(Key& k, const Key& newKey) { k = newKey; }
};
// A subclass template of js::HashMap whose keys and values may be garbage-collected. When
// a key is collected, the table entry disappears, dropping its reference to the value.
//
@@ -296,9 +310,16 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
return nullptr;
}
JSObject* getDelegate(JS::Symbol* sym) const {
return nullptr;
}
private:
void exposeGCThingToActiveJS(const JS::Value& v) const { JS::ExposeValueToActiveJS(v); }
void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); }
void exposeGCThingToActiveJS(JS::Symbol* sym) const {
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(sym));
}
bool keyNeedsMark(JSObject* key) const {
JSObject* delegate = getDelegate(key);
@@ -313,6 +334,10 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
return false;
}
bool keyNeedsMark(JS::Symbol* sym) const {
return false;
}
bool findZoneEdges() override {
// This is overridden by ObjectValueMap.
return true;
@@ -378,6 +403,9 @@ WeakMap_set(JSContext* cx, unsigned argc, Value* vp);
extern bool
WeakMap_delete(JSContext* cx, unsigned argc, Value* vp);
extern bool
SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val);
extern JSObject*
InitWeakMapClass(JSContext* cx, HandleObject obj);
@@ -394,6 +422,16 @@ class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>,
virtual bool findZoneEdges();
};
class SymbolValueMap : public WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
MovableCellHasher<HeapPtr<JS::Symbol*>>>
{
public:
SymbolValueMap(JSContext* cx, JSObject* obj)
: WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
MovableCellHasher<HeapPtr<JS::Symbol*>>>(cx, obj)
{}
};
// Generic weak map for mapping objects to other objects.
class ObjectWeakMap
+2
View File
@@ -120,6 +120,7 @@ EXPORTS.js += [
main_deunified_sources = [
'builtin/AtomicsObject.cpp',
'builtin/Eval.cpp',
'builtin/FinalizationRegistryObject.cpp',
'builtin/intl/Collator.cpp',
'builtin/intl/CommonFunctions.cpp',
'builtin/intl/DateTimeFormat.cpp',
@@ -172,6 +173,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',
+16 -4
View File
@@ -676,7 +676,9 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
AnalyzeEntrainedVariables(cx, script);
#endif
if (!compileOnly) {
if (!JS_ExecuteScript(cx, script))
bool ok = JS_ExecuteScript(cx, script);
JS::ClearWeakRefKeptObjects(cx);
if (!ok)
return false;
int64_t t2 = PRMJ_Now() - t1;
if (printTiming)
@@ -857,6 +859,7 @@ DrainJobQueue(JSContext* cx)
}
sc->jobQueue.clear();
sc->drainingJobQueue = false;
JS::ClearWeakRefKeptObjects(cx);
return true;
}
@@ -3239,6 +3242,15 @@ static const JSClass sandbox_class = {
&sandbox_classOps
};
enum GlobalAppSlot {
GlobalAppSlotModuleMetadataHook,
GlobalAppSlotModuleDynamicImportHook,
GlobalAppSlotCount
};
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
"global application slots overflow");
static void
SetStandardCompartmentOptions(JS::CompartmentOptions& options)
{
@@ -4067,7 +4079,7 @@ ParseModule(JSContext* cx, unsigned argc, Value* vp)
const char16_t* chars = stableChars.twoByteRange().begin().get();
JS::SourceBufferHolder srcBuf(chars, scriptContents->length(),
SourceBufferHolder::NoOwnership);
JS::SourceBufferHolder::NoOwnership);
RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
if (!module)
@@ -4278,7 +4290,7 @@ AbortDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp)
RootedString specifier(cx, args[1].toString());
Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());
cx->setPendingException(args[3]);
cx->setPendingException(args[3], nullptr);
return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
}
@@ -8287,7 +8299,7 @@ main(int argc, char** argv, char** envp)
JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
JS::SetModuleDynamicImportHook(cx, ShellModuleDynamicImportHook);
JS::SetModuleMetadataHook(cx, ShellModuleMetadataHook);
JS::SetModuleMetadataHook(cx, CallModuleMetadataHook);
result = Shell(cx, &op, envp);
+5 -7
View File
@@ -1,15 +1,13 @@
// Manual WeakRef smoke tests for browser console use.
//
// Usage:
// 1) Optionally flip `javascript.options.weakrefs` in about:config and reload.
// 2) Paste this file into the browser console (or load via file://) and run:
// 1) Paste this file into the browser console (or load via file://) and run:
// runWeakRefManual();
// 3) Inspect the returned array of results; no throws or crashes are expected.
// 2) Inspect the returned array of results; no throws or crashes are expected.
//
// Notes:
// - When the pref is ON, deref() should return the target.
// - When the pref is OFF, the stub traces strongly so deref() should also
// return the target.
// - WeakRef is always enabled. Once all strong references are gone, a full GC
// may clear the referent and make deref() return undefined.
function runWeakRefManual() {
const results = [];
@@ -21,7 +19,7 @@ function runWeakRefManual() {
log("initial deref tag", ref.deref()?.tag);
log("repeat deref identity", ref.deref() === target);
// Clear the strong reference; the WeakRef should still be able to return it.
// Clear the strong reference. A future full GC may clear the WeakRef.
target = null;
const afterClear = ref.deref();
@@ -0,0 +1,98 @@
// |reftest| skip-if(!ArrayBuffer.prototype.transfer)
var fixed = new ArrayBuffer(4);
assertEq(fixed.byteLength, 4);
assertEq(fixed.maxByteLength, 4);
assertEq(fixed.resizable, false);
assertEq(fixed.detached, false);
new Uint8Array(fixed).set([1, 2, 3, 4]);
fixed.extra = 17;
assertEq(Array.from(new Uint8Array(fixed)).join(","), "1,2,3,4");
assertThrowsInstanceOf(() => fixed.resize(2), TypeError);
assertEq(ArrayBuffer.prototype.transfer.length, 0);
assertEq(ArrayBuffer.prototype.transferToFixedLength.length, 0);
var resizeArgumentConverted = false;
assertThrowsInstanceOf(() => fixed.resize({ valueOf() { resizeArgumentConverted = true; return 1; } }),
TypeError);
assertEq(resizeArgumentConverted, false);
var resizable = new ArrayBuffer(4, { maxByteLength: 8 });
assertEq(resizable.byteLength, 4);
assertEq(resizable.maxByteLength, 8);
assertEq(resizable.resizable, true);
var bytes = new Uint8Array(resizable);
bytes[0] = 11;
bytes[3] = 44;
resizable.resize(6);
assertEq(resizable.byteLength, 6);
assertEq(new Uint8Array(resizable)[0], 11);
assertEq(new Uint8Array(resizable)[3], 44);
assertEq(new Uint8Array(resizable)[4], 0);
assertThrowsInstanceOf(() => resizable.resize(9), RangeError);
var source = new ArrayBuffer(4);
var sourceBytes = new Uint8Array(source);
sourceBytes[0] = 1;
sourceBytes[1] = 2;
var sourceView = new Uint8Array(source);
var moved = source.transfer(6);
assertEq(source.detached, true);
assertEq(source.byteLength, 0);
assertEq(source.maxByteLength, 0);
assertEq(sourceView.length, 0);
assertEq(moved.byteLength, 6);
assertEq(moved.resizable, false);
assertEq(moved.maxByteLength, 6);
assertEq(new Uint8Array(moved)[0], 1);
assertEq(new Uint8Array(moved)[1], 2);
assertEq(new Uint8Array(moved)[4], 0);
var resizableSource = new ArrayBuffer(4, { maxByteLength: 8 });
new Uint8Array(resizableSource)[0] = 7;
var resizableMoved = resizableSource.transfer();
assertEq(resizableSource.detached, true);
assertEq(resizableSource.resizable, true);
assertEq(resizableMoved.byteLength, 4);
assertEq(resizableMoved.maxByteLength, 8);
assertEq(resizableMoved.resizable, true);
assertEq(new Uint8Array(resizableMoved)[0], 7);
var fixedMoved = resizableMoved.transferToFixedLength(10);
assertEq(resizableMoved.detached, true);
assertEq(fixedMoved.byteLength, 10);
assertEq(fixedMoved.maxByteLength, 10);
assertEq(fixedMoved.resizable, false);
assertEq(new Uint8Array(fixedMoved)[0], 7);
var sliceSource = new ArrayBuffer(8, { maxByteLength: 8 });
var sliceSourceBytes = new Uint8Array(sliceSource);
for (var i = 0; i < sliceSourceBytes.length; i++)
sliceSourceBytes[i] = i + 1;
sliceSource.constructor = {
[Symbol.species]: function(byteLength) {
sliceSource.resize(4);
return new ArrayBuffer(byteLength);
}
};
var sliced = sliceSource.slice(2, 8);
var slicedBytes = new Uint8Array(sliced);
assertEq(sliced.byteLength, 6);
assertEq(slicedBytes[0], 3);
assertEq(slicedBytes[1], 4);
assertEq(slicedBytes[2], 0);
assertEq(slicedBytes[5], 0);
sliceSource.resize(8);
for (var i = 0; i < sliceSourceBytes.length; i++)
sliceSourceBytes[i] = i + 1;
var zeroCopied = sliceSource.slice(6, 8);
assertEq(zeroCopied.byteLength, 2);
assertEq(new Uint8Array(zeroCopied)[0], 0);
assertThrowsInstanceOf(() => new ArrayBuffer(4, { maxByteLength: 3 }), RangeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
@@ -0,0 +1,218 @@
// |reftest| skip-if(!this.SharedArrayBuffer)
var rab = new ArrayBuffer(4, { maxByteLength: 16 });
var tracking = new Uint8Array(rab);
var fixed = new Uint8Array(rab, 1, 2);
var bytes = new Uint8Array(rab);
bytes[1] = 11;
bytes[2] = 22;
assertEq(tracking.length, 4);
assertEq(tracking.byteLength, 4);
assertEq(tracking.byteOffset, 0);
assertEq(fixed.length, 2);
assertEq(fixed.byteLength, 2);
assertEq(fixed.byteOffset, 1);
rab.resize(2);
assertEq(tracking.length, 2);
assertEq(tracking.byteLength, 2);
assertEq(fixed.length, 0);
assertEq(fixed.byteLength, 0);
assertEq(fixed.byteOffset, 0);
assertEq(fixed[0], undefined);
rab.resize(8);
assertEq(tracking.length, 8);
assertEq(tracking.byteLength, 8);
assertEq(fixed.length, 2);
assertEq(fixed.byteLength, 2);
assertEq(fixed.byteOffset, 1);
assertEq(fixed[0], 11);
assertEq(fixed[1], 0);
tracking[6] = 66;
assertEq(new Uint8Array(rab)[6], 66);
var dv = new DataView(rab, 4);
assertEq(dv.byteOffset, 4);
assertEq(dv.byteLength, 4);
dv.setUint8(0, 44);
assertEq(tracking[4], 44);
rab.resize(3);
assertThrowsInstanceOf(() => dv.byteOffset, TypeError);
assertThrowsInstanceOf(() => dv.byteLength, TypeError);
assertThrowsInstanceOf(() => dv.getUint8(0), TypeError);
rab.resize(6);
assertEq(dv.byteOffset, 4);
assertEq(dv.byteLength, 2);
assertEq(dv.getUint8(0), 0);
var fixedDv = new DataView(rab, 4, 2);
rab.resize(5);
assertThrowsInstanceOf(() => fixedDv.byteOffset, TypeError);
assertThrowsInstanceOf(() => fixedDv.byteLength, TypeError);
assertThrowsInstanceOf(() => fixedDv.getUint8(0), TypeError);
rab.resize(6);
assertEq(fixedDv.byteOffset, 4);
assertEq(fixedDv.byteLength, 2);
var methodRab = new ArrayBuffer(4, { maxByteLength: 8 });
var methodFixed = new Uint8Array(methodRab, 2, 2);
methodRab.resize(2);
[
() => methodFixed.at(0),
() => methodFixed.copyWithin(0, 0),
() => methodFixed.entries(),
() => methodFixed.every(x => true),
() => methodFixed.fill(1),
() => methodFixed.filter(x => true),
() => methodFixed.find(x => true),
() => methodFixed.findIndex(x => true),
() => methodFixed.findLast(x => true),
() => methodFixed.findLastIndex(x => true),
() => methodFixed.forEach(x => x),
() => methodFixed.includes(0),
() => methodFixed.indexOf(0),
() => methodFixed.join(","),
() => methodFixed.keys(),
() => methodFixed.lastIndexOf(0),
() => methodFixed.map(x => x),
() => methodFixed.reduce((a, b) => a + b, 0),
() => methodFixed.reduceRight((a, b) => a + b, 0),
() => methodFixed.reverse(),
() => methodFixed.set([1], 0),
() => methodFixed.slice(),
() => methodFixed.some(x => true),
() => methodFixed.sort(),
() => methodFixed.subarray(),
() => methodFixed.toLocaleString(),
() => methodFixed.toReversed(),
() => methodFixed.toSorted(),
() => methodFixed.toString(),
() => methodFixed.values(),
() => methodFixed.with(0, 1),
() => methodFixed[Symbol.iterator](),
].forEach(fn => assertThrowsInstanceOf(fn, TypeError));
methodRab.resize(4);
assertEq(methodFixed.length, 2);
var sourceRab = new ArrayBuffer(4, { maxByteLength: 8 });
var oobSource = new Uint8Array(sourceRab, 2, 2);
sourceRab.resize(2);
assertThrowsInstanceOf(() => new Uint8Array(oobSource), TypeError);
assertThrowsInstanceOf(() => new Uint16Array(oobSource), TypeError);
assertThrowsInstanceOf(() => new Uint8Array(4).set(oobSource), TypeError);
sourceRab.resize(4);
assertEq(new Uint8Array(oobSource).length, 2);
var ctorRab = new ArrayBuffer(8, { maxByteLength: 8 });
var ShrinkingNewTarget = new Proxy(function() {}, {
get(target, prop, receiver) {
if (prop === "prototype") {
ctorRab.resize(2);
return DataView.prototype;
}
return Reflect.get(target, prop, receiver);
}
});
assertThrowsInstanceOf(() => Reflect.construct(DataView, [ctorRab, 4], ShrinkingNewTarget),
RangeError);
var fixedCtorRab = new ArrayBuffer(8, { maxByteLength: 8 });
var FixedShrinkingNewTarget = new Proxy(function() {}, {
get(target, prop, receiver) {
if (prop === "prototype") {
fixedCtorRab.resize(5);
return DataView.prototype;
}
return Reflect.get(target, prop, receiver);
}
});
assertThrowsInstanceOf(() => Reflect.construct(DataView, [fixedCtorRab, 4, 2],
FixedShrinkingNewTarget),
RangeError);
var gsab = new SharedArrayBuffer(4, { maxByteLength: 16 });
var sharedTracking = new Uint8Array(gsab);
assertEq(sharedTracking.length, 4);
gsab.grow(8);
assertEq(sharedTracking.length, 8);
sharedTracking[6] = 33;
assertEq(new Uint8Array(gsab)[6], 33);
var sharedDv = new DataView(gsab);
assertEq(sharedDv.buffer, gsab);
assertEq(sharedDv.byteOffset, 0);
assertEq(sharedDv.byteLength, 8);
sharedDv.setUint8(7, 99);
assertEq(sharedTracking[7], 99);
gsab.grow(12);
assertEq(sharedDv.byteLength, 12);
assertEq(sharedDv.getUint8(7), 99);
var fixedSharedDv = new DataView(gsab, 4, 2);
assertEq(fixedSharedDv.byteOffset, 4);
assertEq(fixedSharedDv.byteLength, 2);
gsab.grow(16);
assertEq(fixedSharedDv.byteOffset, 4);
assertEq(fixedSharedDv.byteLength, 2);
function readLength(view) {
return view.length;
}
function readElement(view, index) {
return view[index];
}
function writeElement(view, index, value) {
view[index] = value;
}
var normal = new Uint8Array(4);
normal[0] = 7;
for (var i = 0; i < 2000; i++) {
assertEq(readLength(normal), 4);
assertEq(readElement(normal, 0), 7);
writeElement(normal, 1, 8);
}
var icRab = new ArrayBuffer(4, { maxByteLength: 8 });
var icTracking = new Uint8Array(icRab);
icTracking[0] = 9;
assertEq(readLength(icTracking), 4);
assertEq(readElement(icTracking, 0), 9);
icRab.resize(0);
assertEq(readLength(icTracking), 0);
assertEq(readElement(icTracking, 0), undefined);
writeElement(icTracking, 0, 1);
icRab.resize(4);
assertEq(readLength(icTracking), 4);
assertEq(readElement(icTracking, 0), 0);
writeElement(icTracking, 0, 12);
assertEq(readElement(icTracking, 0), 12);
var icFixed = new Uint8Array(icRab, 1, 2);
assertEq(readLength(icFixed), 2);
icRab.resize(2);
assertEq(readLength(icFixed), 0);
assertEq(readElement(icFixed, 0), undefined);
writeElement(icFixed, 0, 55);
icRab.resize(4);
assertEq(readLength(icFixed), 2);
assertEq(readElement(icFixed, 0), 0);
var icGsab = new SharedArrayBuffer(4, { maxByteLength: 8 });
var icSharedTracking = new Uint8Array(icGsab);
assertEq(readLength(icSharedTracking), 4);
icGsab.grow(8);
assertEq(readLength(icSharedTracking), 8);
writeElement(icSharedTracking, 5, 77);
assertEq(readElement(icSharedTracking, 5), 77);
if (typeof reportCompare === "function")
reportCompare(true, true);
+77
View File
@@ -0,0 +1,77 @@
// |reftest| skip-if(!this.SharedArrayBuffer || !this.Atomics || !this.drainJobQueue)
if (typeof SharedArrayBuffer === "function" && typeof Atomics === "object" &&
typeof drainJobQueue === "function") {
const sab = new SharedArrayBuffer(4);
const i32 = new Int32Array(sab);
let result = Atomics.waitAsync(i32, 0, 1, 10);
assertEq(result.async, false);
assertEq(result.value, "not-equal");
result = Atomics.waitAsync(i32, 0, 0, 0);
assertEq(result.async, false);
assertEq(result.value, "timed-out");
result = Atomics.waitAsync(i32, 0, 0);
assertEq(result.async, true);
let notified;
result.value.then(value => {
notified = value;
});
assertEq(Atomics.notify(i32, 0, 1), 1);
drainJobQueue();
assertEq(notified, "ok");
result = Atomics.waitAsync(i32, 0, 0, 1);
assertEq(result.async, true);
let timedOut;
result.value.then(value => {
timedOut = value;
});
drainJobQueue();
assertEq(timedOut, "timed-out");
if (typeof BigInt64Array === "function") {
const bigSab = new SharedArrayBuffer(16);
const i64 = new BigInt64Array(bigSab);
const u64 = new BigUint64Array(bigSab);
assertEq(Atomics.store(i64, 0, -1n), -1n);
assertEq(Atomics.load(i64, 0), -1n);
assertEq(Atomics.load(u64, 0), 18446744073709551615n);
assertEq(Atomics.exchange(i64, 0, 7n), -1n);
assertEq(Atomics.compareExchange(i64, 0, 7n, 10n), 7n);
assertEq(Atomics.add(i64, 0, 5n), 10n);
assertEq(Atomics.load(i64, 0), 15n);
assertEq(Atomics.sub(i64, 0, 20n), 15n);
assertEq(Atomics.load(i64, 0), -5n);
assertEq(Atomics.and(u64, 0, 7n), 18446744073709551611n);
assertEq(Atomics.or(u64, 0, 8n), 3n);
assertEq(Atomics.xor(u64, 0, 15n), 11n);
assertThrowsInstanceOf(() => Atomics.waitAsync(u64, 0, 0n), TypeError);
result = Atomics.waitAsync(i64, 1, 1n, 10);
assertEq(result.async, false);
assertEq(result.value, "not-equal");
result = Atomics.waitAsync(i64, 1, 0n, 0);
assertEq(result.async, false);
assertEq(result.value, "timed-out");
let offsetView = new BigInt64Array(bigSab, 8);
result = Atomics.waitAsync(offsetView, 0, 0n);
assertEq(result.async, true);
notified = undefined;
result.value.then(value => {
notified = value;
});
assertEq(Atomics.notify(i64, 1, 1), 1);
drainJobQueue();
assertEq(notified, "ok");
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);
@@ -0,0 +1,31 @@
// |reftest| skip-if(!Promise.withResolvers)
var desc = Object.getOwnPropertyDescriptor(Promise, "withResolvers");
assertEq(desc.enumerable, false);
assertEq(desc.configurable, true);
assertEq(desc.writable, true);
assertEq(Promise.withResolvers.length, 0);
assertEq(Promise.withResolvers.name, "withResolvers");
var capability = Promise.withResolvers();
assertEq(capability.promise instanceof Promise, true);
assertEq(typeof capability.resolve, "function");
assertEq(typeof capability.reject, "function");
assertEqArray(Object.keys(capability), ["promise", "resolve", "reject"]);
capability.resolve(42);
capability.promise.then(v => assertEq(v, 42));
class MyPromise extends Promise {}
var subCapability = Promise.withResolvers.call(MyPromise);
assertEq(subCapability.promise instanceof MyPromise, true);
subCapability.reject("rejected");
subCapability.promise.then(
() => { throw new Error("expected rejection"); },
reason => assertEq(reason, "rejected")
);
assertThrowsInstanceOf(() => Promise.withResolvers.call({}), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
@@ -0,0 +1,40 @@
// |reftest| skip-if(!this.SharedArrayBuffer)
if (typeof SharedArrayBuffer === "function") {
const fixed = new SharedArrayBuffer(4);
assertEq(fixed.byteLength, 4);
assertEq(fixed.maxByteLength, 4);
assertEq(fixed.growable, false);
assertThrowsInstanceOf(() => fixed.grow(4), TypeError);
assertThrowsInstanceOf(() => new SharedArrayBuffer(-1), RangeError);
assertThrowsInstanceOf(() => new SharedArrayBuffer(8, {maxByteLength: 4}), RangeError);
let optionGetterCalled = false;
const growable = new SharedArrayBuffer(4, {
get maxByteLength() {
optionGetterCalled = true;
return 16;
}
});
assertEq(optionGetterCalled, true);
assertEq(growable.byteLength, 4);
assertEq(growable.maxByteLength, 16);
assertEq(growable.growable, true);
const before = new Uint8Array(growable);
before[0] = 37;
assertEq(growable.grow(12), undefined);
assertEq(growable.byteLength, 12);
assertEq(growable.maxByteLength, 16);
const after = new Uint8Array(growable);
assertEq(after.length, 12);
assertEq(after[0], 37);
assertThrowsInstanceOf(() => growable.grow(11), RangeError);
assertThrowsInstanceOf(() => growable.grow(17), RangeError);
}
if (typeof reportCompare === "function")
reportCompare(true, true);
+26
View File
@@ -0,0 +1,26 @@
// |reftest| skip-if(!String.prototype.isWellFormed||!String.prototype.toWellFormed)
assertEq("".isWellFormed(), true);
assertEq("abc".isWellFormed(), true);
assertEq("\uD83D\uDE00".isWellFormed(), true);
assertEq("\uD800".isWellFormed(), false);
assertEq("\uDC00".isWellFormed(), false);
assertEq("\uD800a".isWellFormed(), false);
assertEq("a\uDC00".isWellFormed(), false);
assertEq("\uD800\uD800\uDC00".isWellFormed(), false);
assertEq("abc".toWellFormed(), "abc");
assertEq("\uD83D\uDE00".toWellFormed(), "\uD83D\uDE00");
assertEq("\uD800".toWellFormed(), "\uFFFD");
assertEq("\uDC00".toWellFormed(), "\uFFFD");
assertEq("\uD800a\uDC00".toWellFormed(), "\uFFFDa\uFFFD");
assertEq("\uD800\uD800\uDC00".toWellFormed(), "\uFFFD\uD800\uDC00");
assertEq(String.prototype.isWellFormed.call(123), true);
assertEq(String.prototype.toWellFormed.call({ toString() { return "\uD800x"; } }), "\uFFFDx");
assertThrowsInstanceOf(() => String.prototype.isWellFormed.call(null), TypeError);
assertThrowsInstanceOf(() => String.prototype.toWellFormed.call(undefined), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
@@ -0,0 +1,36 @@
var key = Symbol("weak");
var map = new WeakMap();
assertEq(map.has(key), false);
assertEq(map.get(key), undefined);
assertEq(map.set(key, 13), map);
assertEq(map.has(key), true);
assertEq(map.get(key), 13);
assertEq(map.delete(key), true);
assertEq(map.has(key), false);
var constructedKey = Symbol("constructed");
var constructed = new WeakMap([[constructedKey, 7]]);
assertEq(constructed.get(constructedKey), 7);
var registered = Symbol.for("registered");
assertEq(map.has(registered), false);
assertEq(map.get(registered), undefined);
assertEq(map.delete(registered), false);
assertThrowsInstanceOf(() => map.set(registered, 1), TypeError);
assertThrowsInstanceOf(() => new WeakMap([[registered, 1]]), TypeError);
var setKey = Symbol("set");
var set = new WeakSet([setKey]);
assertEq(set.has(setKey), true);
assertEq(set.delete(setKey), true);
assertEq(set.has(setKey), false);
assertEq(set.add(setKey), set);
assertEq(set.has(setKey), true);
assertEq(set.has(registered), false);
assertEq(set.delete(registered), false);
assertThrowsInstanceOf(() => set.add(registered), TypeError);
assertThrowsInstanceOf(() => new WeakSet([registered]), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
@@ -0,0 +1,34 @@
// |reftest| skip-if(!Object.groupBy||!Map.groupBy)
var objectGroups = Object.groupBy(["a", "bb", "c"], value => value.length);
assertEq(Object.getPrototypeOf(objectGroups), null);
assertEqArray(objectGroups["1"], ["a", "c"]);
assertEqArray(objectGroups["2"], ["bb"]);
var symbol = Symbol();
var symbolGroups = Object.groupBy([1, 2], value => value === 1 ? symbol : "__proto__");
assertEqArray(symbolGroups[symbol], [1]);
assertEqArray(symbolGroups.__proto__, [2]);
var indexes = [];
var mapGroups = Map.groupBy(["a", "bb", "c"], (value, index) => {
indexes.push(index);
return value.length;
});
assertEq(mapGroups instanceof Map, true);
assertEqArray(indexes, [0, 1, 2]);
assertEqArray(mapGroups.get(1), ["a", "c"]);
assertEqArray(mapGroups.get(2), ["bb"]);
var key = {};
var objectKeyGroups = Map.groupBy([1, 2], value => value === 1 ? key : NaN);
assertEqArray(objectKeyGroups.get(key), [1]);
assertEqArray(objectKeyGroups.get(NaN), [2]);
assertThrowsInstanceOf(() => Object.groupBy(null, x => x), TypeError);
assertThrowsInstanceOf(() => Map.groupBy(undefined, x => x), TypeError);
assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError);
assertThrowsInstanceOf(() => Map.groupBy([], null), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
+375 -36
View File
@@ -22,6 +22,7 @@
# include <valgrind/memcheck.h>
#endif
#include <algorithm>
#include "jsapi.h"
#include "jsarray.h"
#include "jscntxt.h"
@@ -45,6 +46,7 @@
#include "vm/Interpreter.h"
#include "vm/SelfHosting.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"
#include "vm/WrapperObject.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmTypes.h"
@@ -170,12 +172,18 @@ static const JSPropertySpec static_properties[] = {
static const JSFunctionSpec prototype_functions[] = {
JS_FN("resize", ArrayBufferObject::fun_resize, 1, 0),
JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
JS_FN("transfer", ArrayBufferObject::fun_transfer, 0, 0),
JS_FN("transferToFixedLength", ArrayBufferObject::fun_transferToFixedLength, 0, 0),
JS_FS_END
};
static const JSPropertySpec prototype_properties[] = {
JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
JS_PSG("detached", ArrayBufferObject::detachedGetter, 0),
JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter, 0),
JS_PSG("resizable", ArrayBufferObject::resizableGetter, 0),
JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
JS_PS_END
};
@@ -252,6 +260,52 @@ ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
ArrayBufferObject::detachedGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isDetached());
return true;
}
bool
ArrayBufferObject::detachedGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, detachedGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
ArrayBufferObject& buffer = args.thisv().toObject().as<ArrayBufferObject>();
args.rval().setInt32(buffer.isDetached() ? 0 : int32_t(buffer.maxByteLength()));
return true;
}
bool
ArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, maxByteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
ArrayBufferObject::resizableGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isResizable());
return true;
}
bool
ArrayBufferObject::resizableGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, resizableGetterImpl>(cx, args);
}
/*
* ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
*/
@@ -264,6 +318,39 @@ ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
GetArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
uint32_t byteLength, uint32_t* maxByteLength,
bool* resizable)
{
*maxByteLength = byteLength;
*resizable = false;
if (!options.isObject())
return true;
RootedObject opts(cx, &options.toObject());
RootedValue maxByteLengthValue(cx);
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
return false;
if (maxByteLengthValue.isUndefined())
return true;
uint64_t max;
if (!ToIndex(cx, maxByteLengthValue, &max))
return false;
if (max > INT32_MAX || max < byteLength) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
*maxByteLength = uint32_t(max);
*resizable = true;
return true;
}
// ES2017 draft 24.1.2.1
bool
@@ -287,12 +374,22 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
}
// Step 3.
uint32_t maxByteLength;
bool resizable;
if (!GetArrayBufferMaxByteLengthOption(cx, args.get(1), uint32_t(byteLength),
&maxByteLength, &resizable))
{
return false;
}
// Step 4.
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
JSObject* bufobj = create(cx, uint32_t(byteLength), proto);
JSObject* bufobj = create(cx, uint32_t(byteLength), BufferContents::createPlain(nullptr),
OwnsData, proto, GenericObject, maxByteLength, resizable);
if (!bufobj)
return false;
args.rval().setObject(*bufobj);
@@ -302,13 +399,66 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
static ArrayBufferObject::BufferContents
AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes)
{
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes);
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes ? nbytes : 1);
if (!p)
ReportOutOfMemory(cx);
return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(p);
}
static bool
ReportArrayBufferNotResizable(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_NOT_RESIZABLE);
return false;
}
static bool
ReportArrayBufferCannotDetach(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_CANNOT_DETACH);
return false;
}
static bool
ReportArrayBufferLengthOutOfRange(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
static bool
ArrayBufferViewFits(ArrayBufferViewObject* view, uint32_t newByteLength)
{
if (view->is<DataViewObject>()) {
DataViewObject& dataView = view->as<DataViewObject>();
uint32_t byteOffset = dataView.byteOffsetMaybeOutOfBounds();
if (byteOffset > newByteLength)
return false;
uint32_t byteLength = dataView.isLengthTracking()
? newByteLength - byteOffset
: dataView.fixedByteLengthMaybeOutOfBounds();
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
}
if (view->is<TypedArrayObject>()) {
TypedArrayObject& typedArray = view->as<TypedArrayObject>();
if (typedArray.isSharedMemory())
return true;
uint32_t byteOffset = typedArray.byteOffsetMaybeOutOfBounds();
if (byteOffset > newByteLength)
return false;
uint32_t byteLength = typedArray.isLengthTracking()
? newByteLength - byteOffset
: typedArray.fixedLengthMaybeOutOfBounds() *
typedArray.bytesPerElement();
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
}
// Outline typed objects don't have a recoverable fixed byte range here.
return false;
}
static void
NoteViewBufferWasDetached(ArrayBufferViewObject* view,
ArrayBufferObject::BufferContents newContents,
@@ -391,7 +541,8 @@ ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState
void
ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
uint8_t* oldDataPointer, BufferContents newContents)
uint8_t* oldDataPointer, BufferContents newContents,
uint32_t newByteLength)
{
MOZ_ASSERT(!view->isSharedMemory());
@@ -402,7 +553,18 @@ ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view
uint8_t* viewDataPointer = view->dataPointerUnshared(nogc);
if (viewDataPointer) {
MOZ_ASSERT(newContents);
ptrdiff_t offset = viewDataPointer - oldDataPointer;
uint32_t offset;
if (view->is<DataViewObject>()) {
offset = view->as<DataViewObject>().byteOffsetMaybeOutOfBounds();
} else if (view->is<TypedArrayObject>()) {
offset = view->as<TypedArrayObject>().byteOffsetMaybeOutOfBounds();
} else {
ptrdiff_t oldOffset = viewDataPointer - oldDataPointer;
MOZ_ASSERT(oldOffset >= 0);
offset = uint32_t(oldOffset);
}
if (offset > newByteLength)
offset = 0;
viewDataPointer = static_cast<uint8_t*>(newContents.data()) + offset;
view->setDataPointerUnshared(viewDataPointer);
}
@@ -428,10 +590,180 @@ ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
auto& innerViews = cx->compartment()->innerViews.get();
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
for (size_t i = 0; i < views->length(); i++)
changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
changeViewContents(cx, (*views)[i], oldDataPointer, newContents, byteLength());
}
if (firstView())
changeViewContents(cx, firstView(), oldDataPointer, newContents);
changeViewContents(cx, firstView(), oldDataPointer, newContents, byteLength());
}
void
ArrayBufferObject::changeContentsForResize(JSContext* cx, BufferContents newContents,
OwnsState ownsState, uint32_t newByteLength)
{
MOZ_RELEASE_ASSERT(!isWasm());
MOZ_ASSERT(!forInlineTypedObject());
uint8_t* oldDataPointer = dataPointer();
setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
setByteLength(newByteLength);
auto& innerViews = cx->compartment()->innerViews.get();
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
for (size_t i = 0; i < views->length(); i++) {
ArrayBufferViewObject* view = (*views)[i];
if (view->is<DataViewObject>() || view->is<TypedArrayObject>())
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
else if (ArrayBufferViewFits(view, newByteLength))
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
else
NoteViewBufferWasDetached(view, newContents, cx);
}
}
if (firstView()) {
if (firstView()->is<DataViewObject>() || firstView()->is<TypedArrayObject>())
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
else if (ArrayBufferViewFits(firstView(), newByteLength))
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
else
NoteViewBufferWasDetached(firstView(), newContents, cx);
}
}
static bool
ResizeArrayBuffer(JSContext* cx, Handle<ArrayBufferObject*> buffer, uint32_t newByteLength)
{
if (!buffer->isResizable())
return ReportArrayBufferNotResizable(cx);
if (buffer->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (newByteLength > buffer->maxByteLength())
return ReportArrayBufferLengthOutOfRange(cx);
if (!buffer->isPlain() || buffer->isPreparedForAsmJS() || buffer->forInlineTypedObject())
return ReportArrayBufferCannotDetach(cx);
if (newByteLength == buffer->byteLength())
return true;
ArrayBufferObject::BufferContents newContents = AllocateArrayBufferContents(cx, newByteLength);
if (!newContents)
return false;
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
if (copyLength > 0)
memcpy(newContents.data(), buffer->dataPointer(), copyLength);
buffer->changeContentsForResize(cx, newContents, ArrayBufferObject::OwnsData, newByteLength);
return true;
}
bool
ArrayBufferObject::fun_resize_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
if (!buffer->isResizable())
return ReportArrayBufferNotResizable(cx);
uint64_t newByteLength;
if (!ToIndex(cx, args.get(0), &newByteLength))
return false;
if (newByteLength > INT32_MAX)
return ReportArrayBufferLengthOutOfRange(cx);
if (!ResizeArrayBuffer(cx, buffer, uint32_t(newByteLength)))
return false;
args.rval().setUndefined();
return true;
}
bool
ArrayBufferObject::fun_resize(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, fun_resize_impl>(cx, args);
}
static bool
ArrayBufferTransfer(JSContext* cx, const CallArgs& args, bool preserveResizability)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
uint32_t newByteLength = buffer->byteLength();
if (args.hasDefined(0)) {
uint64_t newLength;
if (!ToIndex(cx, args.get(0), &newLength))
return false;
if (newLength > INT32_MAX)
return ReportArrayBufferLengthOutOfRange(cx);
newByteLength = uint32_t(newLength);
}
if (buffer->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (buffer->isWasm() || buffer->isPreparedForAsmJS())
return ReportArrayBufferCannotDetach(cx);
bool newResizable = preserveResizability && buffer->isResizable();
uint32_t newMaxByteLength = newResizable ? buffer->maxByteLength() : newByteLength;
if (newResizable && newByteLength > newMaxByteLength)
return ReportArrayBufferLengthOutOfRange(cx);
Rooted<ArrayBufferObject*> newBuffer(cx,
ArrayBufferObject::create(cx, newByteLength, ArrayBufferObject::BufferContents::createPlain(nullptr),
ArrayBufferObject::OwnsData, nullptr, GenericObject,
newMaxByteLength, newResizable));
if (!newBuffer)
return false;
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
if (copyLength > 0)
memcpy(newBuffer->dataPointer(), buffer->dataPointer(), copyLength);
ArrayBufferObject::BufferContents detachedContents =
buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
: buffer->contents();
ArrayBufferObject::detach(cx, buffer, detachedContents);
args.rval().setObject(*newBuffer);
return true;
}
bool
ArrayBufferObject::fun_transfer_impl(JSContext* cx, const CallArgs& args)
{
return ArrayBufferTransfer(cx, args, true);
}
bool
ArrayBufferObject::fun_transfer(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, fun_transfer_impl>(cx, args);
}
bool
ArrayBufferObject::fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args)
{
return ArrayBufferTransfer(cx, args, false);
}
bool
ArrayBufferObject::fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, fun_transferToFixedLength_impl>(cx, args);
}
/*
@@ -887,6 +1219,14 @@ ArrayBufferObject::byteLength() const
return getSlot(BYTE_LENGTH_SLOT).toInt32();
}
uint32_t
ArrayBufferObject::maxByteLength() const
{
if (!isResizable())
return byteLength();
return getSlot(MAX_BYTE_LENGTH_SLOT).toInt32();
}
void
ArrayBufferObject::setByteLength(uint32_t length)
{
@@ -929,7 +1269,7 @@ js::WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
if (buf->is<ArrayBufferObject>())
return buf->as<ArrayBufferObject>().wasmMaxSize();
return Some(buf->as<SharedArrayBufferObject>().byteLength());
return Some(buf->as<SharedArrayBufferObject>().maxByteLength());
}
/* static */ bool
@@ -1035,9 +1375,12 @@ ArrayBufferObject*
ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents contents,
OwnsState ownsState /* = OwnsData */,
HandleObject proto /* = nullptr */,
NewObjectKind newKind /* = GenericObject */)
NewObjectKind newKind /* = GenericObject */,
uint32_t maxByteLength /* = 0 */,
bool resizable /* = false */)
{
MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);
MOZ_ASSERT_IF(resizable, maxByteLength >= nbytes);
// 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
// Refuse to allocate too large buffers, currently limited to ~2 GiB.
@@ -1046,10 +1389,6 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
return nullptr;
}
// If we need to allocate data, try to use a larger object size class so
// that the array buffer's data can be allocated inline with the object.
// The extra space will be left unused by the object's fixed slots and
// available for the buffer's data, see NewObject().
size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
size_t nslots = reservedSlots;
@@ -1066,18 +1405,10 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
}
} else {
MOZ_ASSERT(ownsState == OwnsData);
size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
if (nbytes <= usableSlots * sizeof(Value)) {
int newSlots = (nbytes - 1) / sizeof(Value) + 1;
MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
nslots = reservedSlots + newSlots;
contents = BufferContents::createPlain(nullptr);
} else {
contents = AllocateArrayBufferContents(cx, nbytes);
if (!contents)
return nullptr;
allocated = true;
}
contents = AllocateArrayBufferContents(cx, nbytes);
if (!contents)
return nullptr;
allocated = true;
}
MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
@@ -1098,9 +1429,10 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
if (!contents) {
void* data = obj->inlineDataPointer();
memset(data, 0, nbytes);
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData,
maxByteLength, resizable);
} else {
obj->initialize(nbytes, contents, ownsState);
obj->initialize(nbytes, contents, ownsState, maxByteLength, resizable);
}
return obj;
@@ -1130,25 +1462,28 @@ ArrayBufferObject::createEmpty(JSContext* cx)
bool
ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
MOZ_ASSERT(IsAnyArrayBuffer(args.thisv()));
/*
* This method is only called for |DataView(alienBuf, ...)| which calls
* this as |createDataViewForThis.call(alienBuf, byteOffset, byteLength,
* DataView.prototype)|,
* ergo there must be exactly 3 arguments.
* DataView.prototype, lengthTracking)|,
* ergo there must be exactly 4 arguments.
*/
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args.length() == 4);
uint32_t byteOffset = args[0].toPrivateUint32();
uint32_t byteLength = args[1].toPrivateUint32();
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
bool lengthTracking = args[3].toBoolean();
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx,
&args.thisv().toObject().as<ArrayBufferObjectMaybeShared>());
/*
* Pop off the passed-along prototype and delegate to normal DataViewObject
* construction.
*/
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, &args[2].toObject());
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer,
&args[2].toObject(), lengthTracking);
if (!obj)
return false;
args.rval().setObject(*obj);
@@ -1159,7 +1494,7 @@ bool
ArrayBufferObject::createDataViewForThis(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
return CallNonGenericMethod<IsAnyArrayBuffer, createDataViewForThisImpl>(cx, args);
}
/* static */ ArrayBufferObject::BufferContents
@@ -1528,6 +1863,8 @@ ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
// The data may or may not be inline with the buffer. The buffer
// can only move during a compacting GC, in which case its
// objectMoved hook has already updated the buffer's data pointer.
if (offset > buf.byteLength())
offset = 0;
obj->initPrivate(buf.dataPointer() + offset);
}
}
@@ -1578,6 +1915,8 @@ ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
bool
ArrayBufferViewObject::isSharedMemory()
{
if (is<DataViewObject>())
return as<DataViewObject>().isSharedMemory();
if (is<TypedArrayObject>())
return as<TypedArrayObject>().isSharedMemory();
return false;
@@ -1609,7 +1948,7 @@ ArrayBufferViewObject::bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*
return thisObject->as<TypedArrayObject>().bufferEither();
}
MOZ_ASSERT(thisObject->is<DataViewObject>());
return &thisObject->as<DataViewObject>().arrayBuffer();
return &thisObject->as<DataViewObject>().arrayBufferEither();
}
/* JS Friend API */
@@ -1860,7 +2199,7 @@ JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoChe
if (!obj)
return nullptr;
if (obj->is<DataViewObject>()) {
*isSharedMemory = false;
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
return obj->as<DataViewObject>().dataPointer();
}
TypedArrayObject& ta = obj->as<TypedArrayObject>();
@@ -1930,7 +2269,7 @@ js::GetArrayBufferViewLengthAndData(JSObject* obj, uint32_t* length, bool* isSha
: obj->as<TypedArrayObject>().byteLength();
if (obj->is<DataViewObject>()) {
*isSharedMemory = false;
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
*data = static_cast<uint8_t*>(obj->as<DataViewObject>().dataPointer());
}
else {
+36 -6
View File
@@ -82,7 +82,7 @@ ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
class ArrayBufferObjectMaybeShared : public NativeObject
{
public:
uint32_t byteLength() {
uint32_t byteLength() const {
return AnyArrayBufferByteLength(this);
}
@@ -128,15 +128,22 @@ typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObj
class ArrayBufferObject : public ArrayBufferObjectMaybeShared
{
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool resizableGetterImpl(JSContext* cx, const CallArgs& args);
static bool detachedGetterImpl(JSContext* cx, const CallArgs& args);
static bool fun_slice_impl(JSContext* cx, const CallArgs& args);
static bool fun_resize_impl(JSContext* cx, const CallArgs& args);
static bool fun_transfer_impl(JSContext* cx, const CallArgs& args);
static bool fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args);
public:
static const uint8_t DATA_SLOT = 0;
static const uint8_t BYTE_LENGTH_SLOT = 1;
static const uint8_t FIRST_VIEW_SLOT = 2;
static const uint8_t FLAGS_SLOT = 3;
static const uint8_t MAX_BYTE_LENGTH_SLOT = 4;
static const uint8_t RESERVED_SLOTS = 4;
static const uint8_t RESERVED_SLOTS = 5;
static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
@@ -189,7 +196,11 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
// This PLAIN or WASM buffer has been prepared for asm.js and cannot
// henceforth be transferred/detached.
FOR_ASMJS = 0x40
FOR_ASMJS = 0x40,
// This buffer was created with [[ArrayBufferMaxByteLength]] and can
// be resized up to that maximum.
RESIZABLE = 0x80
};
static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
@@ -230,8 +241,14 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
static const Class class_;
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool resizableGetter(JSContext* cx, unsigned argc, Value* vp);
static bool detachedGetter(JSContext* cx, unsigned argc, Value* vp);
static bool fun_slice(JSContext* cx, unsigned argc, Value* vp);
static bool fun_resize(JSContext* cx, unsigned argc, Value* vp);
static bool fun_transfer(JSContext* cx, unsigned argc, Value* vp);
static bool fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp);
static bool fun_isView(JSContext* cx, unsigned argc, Value* vp);
@@ -243,7 +260,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
BufferContents contents,
OwnsState ownsState = OwnsData,
HandleObject proto = nullptr,
NewObjectKind newKind = GenericObject);
NewObjectKind newKind = GenericObject,
uint32_t maxByteLength = 0,
bool resizable = false);
static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes,
HandleObject proto = nullptr,
NewObjectKind newKind = GenericObject);
@@ -294,6 +313,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
void setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState);
void changeContents(JSContext* cx, BufferContents newContents, OwnsState ownsState);
void changeContentsForResize(JSContext* cx, BufferContents newContents,
OwnsState ownsState, uint32_t newByteLength);
// Detach this buffer from its original memory. (This necessarily makes
// views of this buffer unusable for modifying that original memory.)
@@ -302,7 +323,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
private:
void changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
uint8_t* oldDataPointer, BufferContents newContents);
uint8_t* oldDataPointer, BufferContents newContents,
uint32_t newByteLength);
void setFirstView(ArrayBufferViewObject* view);
uint8_t* inlineDataPointer() const;
@@ -311,6 +333,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
uint8_t* dataPointer() const;
SharedMem<uint8_t*> dataPointerShared() const;
uint32_t byteLength() const;
uint32_t maxByteLength() const;
BufferContents contents() const {
return BufferContents(dataPointer(), bufferKind());
@@ -334,6 +357,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
bool isWasm() const { return bufferKind() == WASM; }
bool isMapped() const { return bufferKind() == MAPPED; }
bool isDetached() const { return flags() & DETACHED; }
bool isResizable() const { return flags() & RESIZABLE; }
bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
// WebAssembly support:
@@ -391,12 +415,17 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
void setIsDetached() { setFlags(flags() | DETACHED); }
void setIsPreparedForAsmJS() { setFlags(flags() | FOR_ASMJS); }
void setIsResizable() { setFlags(flags() | RESIZABLE); }
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState,
uint32_t maxByteLength = 0, bool resizable = false) {
setByteLength(byteLength);
setFlags(0);
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(maxByteLength ? maxByteLength : byteLength));
setFirstView(nullptr);
setDataPointer(contents, ownsState);
if (resizable)
setIsResizable();
}
// Note: initialize() may be called after initEmpty(); initEmpty() must
@@ -404,6 +433,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
void initEmpty() {
setByteLength(0);
setFlags(0);
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(0));
setFirstView(nullptr);
setDataPointer(BufferContents::createPlain(nullptr), DoesntOwnData);
}
+1
View File
@@ -248,6 +248,7 @@
macro(lookupSetter, lookupSetter, "__lookupSetter__") \
macro(MapConstructorInit, MapConstructorInit, "MapConstructorInit") \
macro(MapIterator, MapIterator, "Map Iterator") \
macro(maxByteLength, maxByteLength, "maxByteLength") \
macro(maximumFractionDigits, maximumFractionDigits, "maximumFractionDigits") \
macro(maximumSignificantDigits, maximumSignificantDigits, "maximumSignificantDigits") \
macro(message, message, "message") \
+2
View File
@@ -17,6 +17,7 @@
#include "builtin/AtomicsObject.h"
#include "builtin/BigInt.h"
#include "builtin/Eval.h"
#include "builtin/FinalizationRegistryObject.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"
@@ -538,6 +539,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
InitBareSymbolCtor(cx, global) &&
InitBareFinalizationRegistryCtor(cx, global) &&
InitBareWeakMapCtor(cx, global) &&
InitBareWeakRefCtor(cx, global) &&
InitStopIterationClass(cx, global) &&
+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)
@@ -967,8 +969,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,
@@ -1418,13 +1419,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:
+20 -2
View File
@@ -43,6 +43,22 @@ static const ObjectElements emptyElementsHeaderShared(0, 0, ObjectElements::Shar
HeapSlot* const js::emptyObjectElementsShared =
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
static const ObjectElements emptyElementsHeaderResizableOrGrowable(
0, 0, ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
/* Objects with no elements share one empty set of elements. */
HeapSlot* const js::emptyObjectElementsResizableOrGrowable =
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderResizableOrGrowable) +
sizeof(ObjectElements));
static const ObjectElements emptyElementsHeaderSharedResizableOrGrowable(
0, 0, ObjectElements::SHARED_MEMORY | ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
/* Objects with no elements share one empty set of elements. */
HeapSlot* const js::emptyObjectElementsSharedResizableOrGrowable =
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderSharedResizableOrGrowable) +
sizeof(ObjectElements));
#ifdef DEBUG
@@ -61,10 +77,12 @@ ObjectElements::ConvertElementsToDoubles(JSContext* cx, uintptr_t elementsPtr)
* This function is infallible, but has a fallible interface so that it can
* be called directly from Ion code. Only arrays can have their dense
* elements converted to doubles, and arrays never have empty elements.
*/
*/
HeapSlot* elementsHeapPtr = (HeapSlot*) elementsPtr;
MOZ_ASSERT(elementsHeapPtr != emptyObjectElements &&
elementsHeapPtr != emptyObjectElementsShared);
elementsHeapPtr != emptyObjectElementsShared &&
elementsHeapPtr != emptyObjectElementsResizableOrGrowable &&
elementsHeapPtr != emptyObjectElementsSharedResizableOrGrowable);
ObjectElements* header = ObjectElements::fromElements(elementsHeapPtr);
MOZ_ASSERT(!header->shouldConvertDoubleElements());
+25 -1
View File
@@ -185,6 +185,11 @@ class ObjectElements
// These elements are set to integrity level "frozen".
FROZEN = 0x10,
// For TypedArrays only: this TypedArray views a resizable
// ArrayBuffer or growable SharedArrayBuffer. JIT fast paths with
// cached length/data assumptions must fall back for these objects.
RESIZABLE_OR_GROWABLE_BUFFER = 0x20,
};
private:
@@ -255,6 +260,10 @@ class ObjectElements
: flags(SHARED_MEMORY), initializedLength(0), capacity(capacity), length(length)
{}
constexpr ObjectElements(uint32_t capacity, uint32_t length, uint32_t flags)
: flags(flags), initializedLength(0), capacity(capacity), length(length)
{}
HeapSlot* elements() {
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
}
@@ -269,6 +278,10 @@ class ObjectElements
return flags & SHARED_MEMORY;
}
bool hasResizableOrGrowableBuffer() const {
return flags & RESIZABLE_OR_GROWABLE_BUFFER;
}
GCPtrNativeObject& ownerObject() const {
MOZ_ASSERT(isCopyOnWrite());
return *(GCPtrNativeObject*)(&elements()[initializedLength]);
@@ -326,6 +339,8 @@ static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == sizeof(Obj
*/
extern HeapSlot* const emptyObjectElements;
extern HeapSlot* const emptyObjectElementsShared;
extern HeapSlot* const emptyObjectElementsResizableOrGrowable;
extern HeapSlot* const emptyObjectElementsSharedResizableOrGrowable;
struct Class;
class GCMarker;
@@ -480,6 +495,12 @@ class NativeObject : public ShapedObject
elements_ = emptyObjectElementsShared;
}
void setHasResizableOrGrowableBuffer() {
MOZ_ASSERT(elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared);
elements_ = isSharedMemory() ? emptyObjectElementsSharedResizableOrGrowable
: emptyObjectElementsResizableOrGrowable;
}
bool isInWholeCellBuffer() const {
const gc::TenuredCell* cell = &asTenured();
gc::ArenaCellSet* cells = cell->arena()->bufferedCells;
@@ -1254,7 +1275,10 @@ class NativeObject : public ShapedObject
}
inline bool hasEmptyElements() const {
return elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared;
return elements_ == emptyObjectElements ||
elements_ == emptyObjectElementsShared ||
elements_ == emptyObjectElementsResizableOrGrowable ||
elements_ == emptyObjectElementsSharedResizableOrGrowable;
}
/*
+134
View File
@@ -194,6 +194,8 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
simulator_(nullptr),
#endif
scriptAndCountsVector(nullptr),
weakRefKeptObjects(nullptr),
finalizationRegistryCleanupJobs(nullptr),
lcovOutput(),
NaNValue(DoubleNaNValue()),
negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
@@ -372,6 +374,9 @@ JSRuntime::destroyRuntime()
MOZ_ASSERT(!isHeapBusy());
MOZ_ASSERT(childRuntimeCount == 0);
clearWeakRefKeptObjects();
clearFinalizationRegistryCleanupJobs();
fx.destroyInstance();
sharedIntlData.destroyInstance();
@@ -460,6 +465,129 @@ JSRuntime::destroyRuntime()
#endif
}
static bool
SameWeakRefKeptObject(const JS::Value& kept, JS::HandleValue target)
{
MOZ_ASSERT(kept.isObject() || kept.isSymbol());
MOZ_ASSERT(target.isObject() || target.isSymbol());
if (kept.isObject())
return target.isObject() && &kept.toObject() == &target.toObject();
return target.isSymbol() && kept.toSymbol() == target.toSymbol();
}
bool
JSRuntime::addWeakRefKeptObject(JSContext* cx, JS::HandleValue target)
{
MOZ_ASSERT(cx->runtime() == this);
MOZ_ASSERT(target.isObject() || target.isSymbol());
MOZ_ASSERT(!isHeapBusy());
if (!weakRefKeptObjects) {
auto* keptObjects =
cx->new_<JS::PersistentRooted<js::WeakRefKeptObjectVector>>(
cx, js::WeakRefKeptObjectVector(js::SystemAllocPolicy()));
if (!keptObjects)
return false;
weakRefKeptObjects = keptObjects;
}
for (size_t i = 0; i < weakRefKeptObjects->length(); i++) {
const JS::Value& kept = (*weakRefKeptObjects)[i];
if (SameWeakRefKeptObject(kept, target))
return true;
}
if (!weakRefKeptObjects->append(target.get())) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
void
JSRuntime::clearWeakRefKeptObjects()
{
MOZ_ASSERT(!isHeapBusy());
if (!weakRefKeptObjects)
return;
defaultFreeOp()->delete_(weakRefKeptObjects);
weakRefKeptObjects = nullptr;
}
bool
JSRuntime::enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job)
{
MOZ_ASSERT(cx->runtime() == this);
MOZ_ASSERT(job);
MOZ_ASSERT(job->is<JSFunction>());
MOZ_ASSERT(isHeapBusy());
if (!finalizationRegistryCleanupJobs) {
auto* cleanupJobs =
cx->new_<JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>>(
cx, js::FinalizationRegistryCleanupJobVector(js::SystemAllocPolicy()));
if (!cleanupJobs)
return false;
finalizationRegistryCleanupJobs = cleanupJobs;
}
for (size_t i = 0; i < finalizationRegistryCleanupJobs->length(); i++) {
if ((*finalizationRegistryCleanupJobs)[i] == job)
return true;
}
if (!finalizationRegistryCleanupJobs->append(job)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
bool
JSRuntime::drainFinalizationRegistryCleanupJobs(JSContext* cx)
{
MOZ_ASSERT(cx->runtime() == this);
MOZ_ASSERT(!isHeapBusy());
if (!finalizationRegistryCleanupJobs)
return true;
if (!enqueuePromiseJobCallback) {
finalizationRegistryCleanupJobs->clear();
return true;
}
size_t length = finalizationRegistryCleanupJobs->length();
for (size_t i = 0; i < length; i++) {
RootedFunction job(cx, &(*finalizationRegistryCleanupJobs)[i]->as<JSFunction>());
if (!enqueuePromiseJob(cx, job, nullptr, nullptr))
return false;
}
finalizationRegistryCleanupJobs->clear();
return true;
}
void
JSRuntime::clearFinalizationRegistryCleanupJobs()
{
MOZ_ASSERT(!isHeapBusy());
if (!finalizationRegistryCleanupJobs)
return;
defaultFreeOp()->delete_(finalizationRegistryCleanupJobs);
finalizationRegistryCleanupJobs = nullptr;
}
void
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
{
@@ -673,6 +801,12 @@ JSRuntime::traceSharedIntlData(JSTracer* trc)
void
JSRuntime::triggerActivityCallback(bool active)
{
if (active) {
gc.notifyJSExecutionStart();
} else {
gc.notifyJSExecutionEnd();
}
if (!activityCallback)
return;
+15
View File
@@ -360,6 +360,8 @@ class PerThreadData
};
using ScriptAndCountsVector = GCVector<ScriptAndCounts, 0, SystemAllocPolicy>;
using WeakRefKeptObjectVector = JS::GCVector<JS::Value, 0, SystemAllocPolicy>;
using FinalizationRegistryCleanupJobVector = JS::GCVector<JSObject*, 0, SystemAllocPolicy>;
class AutoLockForExclusiveAccess;
} // namespace js
@@ -887,6 +889,19 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Strong references on scripts held for PCCount profiling API. */
JS::PersistentRooted<js::ScriptAndCountsVector>* scriptAndCountsVector;
/* Strong references to live WeakRef targets kept until the next job boundary. */
JS::PersistentRooted<js::WeakRefKeptObjectVector>* weakRefKeptObjects;
[[nodiscard]] bool addWeakRefKeptObject(JSContext* cx, JS::HandleValue target);
void clearWeakRefKeptObjects();
/* Cleanup jobs produced by FinalizationRegistry sweeping, enqueued after GC. */
JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>* finalizationRegistryCleanupJobs;
[[nodiscard]] bool enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job);
[[nodiscard]] bool drainFinalizationRegistryCleanupJobs(JSContext* cx);
void clearFinalizationRegistryCleanupJobs();
/* Code coverage output. */
js::coverage::LCovRuntime lcovOutput;
+65
View File
@@ -38,6 +38,7 @@
#include "builtin/SelfHostingDefines.h"
#include "builtin/Stream.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakRefObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
#include "gc/Policy.h"
@@ -105,6 +106,14 @@ intrinsic_IsObject(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
intrinsic_CanBeHeldWeakly(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(CanBeHeldWeakly(args.get(0)));
return true;
}
static bool
intrinsic_IsArray(JSContext* cx, unsigned argc, Value* vp)
{
@@ -270,6 +279,20 @@ intrinsic_GetBuiltinConstructor(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
intrinsic_NewMap(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
Rooted<MapObject*> map(cx, MapObject::create(cx));
if (!map)
return false;
args.rval().setObject(*map);
return true;
}
static bool
intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp)
{
@@ -1287,6 +1310,36 @@ intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer(JSContext* cx, unsigned arg
return true;
}
static bool
intrinsic_TypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedObject obj(cx, &args[0].toObject());
MOZ_ASSERT(obj->is<TypedArrayObject>());
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
return true;
}
static bool
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
JSObject* obj = CheckedUnwrap(&args[0].toObject());
if (!obj) {
JS_ReportErrorASCII(cx, "Permission denied to access object");
return false;
}
MOZ_ASSERT(obj->is<TypedArrayObject>());
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
return true;
}
static bool
intrinsic_MoveTypedArrayElements(JSContext* cx, unsigned argc, Value* vp)
{
@@ -1409,6 +1462,10 @@ intrinsic_SetFromTypedArrayApproach(JSContext* cx, unsigned argc, Value* vp)
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (unsafeTypedArrayCrossCompartment->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
// Steps 21, 23.
uint32_t unsafeSrcLengthCrossCompartment = unsafeTypedArrayCrossCompartment->length();
@@ -2255,7 +2312,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("std_Math_min", math_min, 2,0, MathMin),
JS_INLINABLE_FN("std_Math_abs", math_abs, 1,0, MathAbs),
JS_FN("std_Map_create", intrinsic_NewMap, 0,0),
JS_FN("std_Map_get", MapObject::get, 1,0),
JS_FN("std_Map_has", MapObject::has, 1,0),
JS_FN("std_Map_set", MapObject::set, 2,0),
JS_FN("std_Map_iterator", MapObject::entries, 0,0),
JS_FN("std_Number_valueOf", num_valueOf, 0,0),
@@ -2303,6 +2363,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
// Helper funtions after this point.
JS_INLINABLE_FN("ToObject", intrinsic_ToObject, 1,0, IntrinsicToObject),
JS_INLINABLE_FN("IsObject", intrinsic_IsObject, 1,0, IntrinsicIsObject),
JS_FN("CanBeHeldWeakly", intrinsic_CanBeHeldWeakly, 1,0),
JS_INLINABLE_FN("IsArray", intrinsic_IsArray, 1,0, ArrayIsArray),
JS_INLINABLE_FN("IsWrappedArrayConstructor", intrinsic_IsWrappedArrayConstructor, 1,0,
IntrinsicIsWrappedArrayConstructor),
@@ -2462,6 +2523,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
1, 0, IntrinsicPossiblyWrappedTypedArrayLength),
JS_FN("PossiblyWrappedTypedArrayHasDetachedBuffer",
intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer, 1, 0),
JS_FN("TypedArrayIsOutOfBounds",
intrinsic_TypedArrayIsOutOfBounds, 1, 0),
JS_FN("PossiblyWrappedTypedArrayIsOutOfBounds",
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds, 1, 0),
JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4,0),
JS_FN("SetFromTypedArrayApproach",intrinsic_SetFromTypedArrayApproach, 4, 0),
+156 -15
View File
@@ -8,6 +8,7 @@
#include "mozilla/Atomics.h"
#include "jsfriendapi.h"
#include "jsnum.h"
#include "jsprf.h"
#ifdef XP_WIN
@@ -104,15 +105,18 @@ SharedArrayAllocSize(uint32_t length)
}
SharedArrayRawBuffer*
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable)
{
// The value (uint32_t)-1 is used as a signal in various places,
// so guard against it on principle.
MOZ_ASSERT(length != (uint32_t)-1);
MOZ_ASSERT(maxLength != (uint32_t)-1);
MOZ_ASSERT(maxLength >= length);
// Add a page for the header and round to a page boundary.
uint32_t allocSize = SharedArrayAllocSize(length);
if (allocSize <= length)
uint32_t allocationLength = growable ? maxLength : length;
uint32_t allocSize = SharedArrayAllocSize(allocationLength);
if (allocSize <= allocationLength)
return nullptr;
// Test >= to guard against the case where multiple extant runtimes
@@ -127,7 +131,8 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
}
}
bool preparedForAsmJS = jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
bool preparedForAsmJS =
!growable && jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
void* p = nullptr;
if (preparedForAsmJS) {
@@ -161,8 +166,9 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
uint8_t* base = buffer - sizeof(SharedArrayRawBuffer);
SharedArrayRawBuffer* rawbuf = new (base) SharedArrayRawBuffer(buffer, length, preparedForAsmJS);
MOZ_ASSERT(rawbuf->length == length); // Deallocation needs this
SharedArrayRawBuffer* rawbuf =
new (base) SharedArrayRawBuffer(buffer, length, maxLength, growable, preparedForAsmJS);
MOZ_ASSERT(rawbuf->allocatedByteLength() == allocationLength); // Deallocation needs this.
return rawbuf;
}
@@ -201,7 +207,7 @@ SharedArrayRawBuffer::dropReference()
MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
uint8_t* address = p.unwrap(/*safe - only reference*/);
uint32_t allocSize = SharedArrayAllocSize(this->length);
uint32_t allocSize = SharedArrayAllocSize(this->allocatedByteLength());
if (this->preparedForAsmJS) {
uint32_t mappedSize = SharedArrayMappedSize(allocSize);
@@ -221,6 +227,23 @@ SharedArrayRawBuffer::dropReference()
numLive--;
}
bool
SharedArrayRawBuffer::growTo(uint32_t newLength)
{
MOZ_ASSERT(growable);
MOZ_ASSERT(newLength <= maxLength);
for (;;) {
uint32_t oldLength = length;
if (newLength < oldLength)
return false;
if (newLength == oldLength)
return true;
if (length.compareExchange(oldLength, newLength))
return true;
}
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
@@ -237,6 +260,112 @@ SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* v
return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
args.rval().setInt32(args.thisv().toObject().as<SharedArrayBufferObject>().maxByteLength());
return true;
}
bool
SharedArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsSharedArrayBuffer, maxByteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::growableGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
args.rval().setBoolean(args.thisv().toObject().as<SharedArrayBufferObject>().isGrowable());
return true;
}
bool
SharedArrayBufferObject::growableGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsSharedArrayBuffer, growableGetterImpl>(cx, args);
}
static bool
ReportSharedArrayBufferNotGrowable(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_NOT_GROWABLE);
return false;
}
static bool
ReportSharedArrayBufferLengthOutOfRange(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
return false;
}
static bool
GetSharedArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
uint32_t byteLength, uint32_t* maxByteLength,
bool* growable)
{
*maxByteLength = byteLength;
*growable = false;
if (!options.isObject())
return true;
RootedObject opts(cx, &options.toObject());
RootedValue maxByteLengthValue(cx);
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
return false;
if (maxByteLengthValue.isUndefined())
return true;
uint64_t max;
if (!ToIndex(cx, maxByteLengthValue, &max))
return false;
if (max > INT32_MAX || max < byteLength)
return ReportSharedArrayBufferLengthOutOfRange(cx);
*maxByteLength = uint32_t(max);
*growable = true;
return true;
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::fun_grow_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
Rooted<SharedArrayBufferObject*> buffer(cx,
&args.thisv().toObject().as<SharedArrayBufferObject>());
if (!buffer->isGrowable())
return ReportSharedArrayBufferNotGrowable(cx);
uint64_t newByteLength;
if (!ToIndex(cx, args.get(0), &newByteLength))
return false;
if (newByteLength > INT32_MAX)
return ReportSharedArrayBufferLengthOutOfRange(cx);
uint32_t newLength = uint32_t(newByteLength);
if (newLength > buffer->maxByteLength() || !buffer->growTo(newLength))
return ReportSharedArrayBufferLengthOutOfRange(cx);
args.rval().setUndefined();
return true;
}
bool
SharedArrayBufferObject::fun_grow(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsSharedArrayBuffer, fun_grow_impl>(cx, args);
}
bool
SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
{
@@ -245,11 +374,19 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer"))
return false;
uint64_t length64;
if (!ToIndex(cx, args.get(0), &length64))
return false;
// Bugs 1068458, 1161298: Limit length to 2^31-1.
uint32_t length;
bool overflow_unused;
if (!ToLengthClamped(cx, args.get(0), &length, &overflow_unused) || length > INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
if (length64 > INT32_MAX)
return ReportSharedArrayBufferLengthOutOfRange(cx);
uint32_t length = uint32_t(length64);
uint32_t maxByteLength;
bool growable;
if (!GetSharedArrayBufferMaxByteLengthOption(cx, args.get(1), length,
&maxByteLength, &growable))
{
return false;
}
@@ -258,7 +395,7 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
JSObject* bufobj = New(cx, length, proto);
JSObject* bufobj = New(cx, length, maxByteLength, growable, proto);
if (!bufobj)
return false;
args.rval().setObject(*bufobj);
@@ -266,9 +403,10 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
}
SharedArrayBufferObject*
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, HandleObject proto)
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable,
HandleObject proto)
{
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length);
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length, maxLength, growable);
if (!buffer)
return nullptr;
@@ -341,7 +479,7 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi
// just live with the risk.
const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
info->objectsNonHeapElementsShared +=
buf.byteLength() / buf.rawBufferObject()->refcount();
buf.rawBufferObject()->allocatedByteLength() / buf.rawBufferObject()->refcount();
}
/* static */ void
@@ -409,12 +547,15 @@ static const JSPropertySpec static_properties[] = {
};
static const JSFunctionSpec prototype_functions[] = {
JS_FN("grow", SharedArrayBufferObject::fun_grow, 1, 0),
JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0),
JS_FS_END
};
static const JSPropertySpec prototype_properties[] = {
JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
JS_PSG("growable", SharedArrayBufferObject::growableGetter, 0),
JS_PSG("maxByteLength", SharedArrayBufferObject::maxByteLengthGetter, 0),
JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
JS_PS_END
};
+48 -3
View File
@@ -44,7 +44,9 @@ class SharedArrayRawBuffer
{
private:
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
uint32_t length;
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> length;
uint32_t maxLength;
bool growable;
bool preparedForAsmJS;
// A list of structures representing tasks waiting on some
@@ -52,9 +54,12 @@ class SharedArrayRawBuffer
FutexWaiter* waiters_;
protected:
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, bool preparedForAsmJS)
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, uint32_t maxLength, bool growable,
bool preparedForAsmJS)
: refcount_(1),
length(length),
maxLength(maxLength),
growable(growable),
preparedForAsmJS(preparedForAsmJS),
waiters_(nullptr)
{
@@ -62,7 +67,11 @@ class SharedArrayRawBuffer
}
public:
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length);
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length, uint32_t maxLength,
bool growable);
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length) {
return New(cx, length, length, false);
}
// This may be called from multiple threads. The caller must take
// care of mutual exclusion.
@@ -85,6 +94,20 @@ class SharedArrayRawBuffer
return length;
}
uint32_t maxByteLength() const {
return growable ? maxLength : byteLength();
}
uint32_t allocatedByteLength() const {
return growable ? maxLength : byteLength();
}
bool isGrowable() const {
return growable;
}
[[nodiscard]] bool growTo(uint32_t newLength);
bool isPreparedForAsmJS() const {
return preparedForAsmJS;
}
@@ -117,6 +140,9 @@ class SharedArrayRawBuffer
class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
{
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool growableGetterImpl(JSContext* cx, const CallArgs& args);
static bool fun_grow_impl(JSContext* cx, const CallArgs& args);
public:
// RAWBUF_SLOT holds a pointer (as "private" data) to the
@@ -128,13 +154,23 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
static const Class class_;
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool growableGetter(JSContext* cx, unsigned argc, Value* vp);
static bool fun_grow(JSContext* cx, unsigned argc, Value* vp);
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
// Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
static SharedArrayBufferObject* New(JSContext* cx,
uint32_t length,
uint32_t maxLength,
bool growable,
HandleObject proto = nullptr);
static SharedArrayBufferObject* New(JSContext* cx,
uint32_t length,
HandleObject proto = nullptr) {
return New(cx, length, length, false, proto);
}
// Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer.
static SharedArrayBufferObject* New(JSContext* cx,
@@ -164,6 +200,15 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
uint32_t byteLength() const {
return rawBufferObject()->byteLength();
}
uint32_t maxByteLength() const {
return rawBufferObject()->maxByteLength();
}
bool isGrowable() const {
return rawBufferObject()->isGrowable();
}
[[nodiscard]] bool growTo(uint32_t newLength) {
return rawBufferObject()->growTo(newLength);
}
bool isPreparedForAsmJS() const {
return rawBufferObject()->isPreparedForAsmJS();
}
+1
View File
@@ -45,6 +45,7 @@
#include "builtin/MapObject.h"
#include "js/Date.h"
#include "js/GCHashTable.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/SavedFrame.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"
+53 -10
View File
@@ -761,16 +761,40 @@ class TypedArrayMethods
if (!ToInt32(cx, args[1], &offset))
return false;
if (offset < 0 || uint32_t(offset) > target->length()) {
// the given offset is bogus
if (offset < 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
}
if (target->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (target->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
uint32_t targetLength = target->length();
if (uint32_t(offset) > targetLength) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
RootedObject arg0(cx, &args[0].toObject());
if (arg0->is<TypedArrayObject>()) {
if (arg0->as<TypedArrayObject>().length() > target->length() - offset) {
Rooted<TypedArrayObject*> source(cx, &arg0->as<TypedArrayObject>());
if (source->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (source->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
if (source->length() > targetLength - offset) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
@@ -782,7 +806,7 @@ class TypedArrayMethods
if (!GetLengthProperty(cx, arg0, &len))
return false;
if (uint32_t(offset) > target->length() || len > target->length() - offset) {
if (len > targetLength - offset) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
@@ -795,13 +819,32 @@ class TypedArrayMethods
return true;
}
static bool
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
uint32_t offset = 0)
{
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
static bool
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
uint32_t offset = 0)
{
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
bool isShared = target->isSharedMemory() || source->as<TypedArrayObject>().isSharedMemory();
if (target->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (target->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
Rooted<TypedArrayObject*> sourceArray(cx, &source->as<TypedArrayObject>());
if (sourceArray->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (sourceArray->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
bool isShared = target->isSharedMemory() || sourceArray->isSharedMemory();
switch (target->type()) {
case Scalar::Int8:
+102 -32
View File
@@ -482,8 +482,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static TypedArrayObject*
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, uint32_t len,
HandleObject proto)
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
uint32_t byteOffset, uint32_t len, HandleObject proto,
bool lengthTracking = false)
{
MOZ_ASSERT_IF(!buffer, byteOffset == 0);
@@ -508,12 +509,19 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr;
bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get());
bool hasResizableOrGrowableBuffer =
buffer &&
((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
(buffer->is<SharedArrayBufferObject>() &&
buffer->as<SharedArrayBufferObject>().isGrowable()));
obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer));
// This is invariant. Self-hosting code that sets BUFFER_SLOT
// (if it does) must maintain it, should it need to.
if (isSharedMemory)
obj->setIsSharedMemory();
if (hasResizableOrGrowableBuffer)
obj->setHasResizableOrGrowableBuffer();
if (buffer) {
obj->initViewData(buffer->dataPointerEither() + byteOffset);
@@ -547,7 +555,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
#endif
}
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(len));
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT,
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
: int32_t(len)));
obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
#ifdef DEBUG
@@ -896,6 +906,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr; // invalid byteOffset
}
bool lengthTracking = false;
uint32_t len;
if (lengthInt == -1) {
len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
@@ -904,6 +915,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
}
if ((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
(buffer->is<SharedArrayBufferObject>() &&
buffer->as<SharedArrayBufferObject>().isGrowable()))
{
lengthTracking = true;
}
} else {
len = uint32_t(lengthInt);
}
@@ -922,7 +939,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr; // byteOffset + len is too big for the arraybuffer
}
return makeInstance(cx, buffer, byteOffset, len, proto);
return makeInstance(cx, buffer, byteOffset, len, proto, lengthTracking);
}
static bool
@@ -1318,6 +1335,10 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, b
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return nullptr;
}
if (srcArray->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return nullptr;
}
// Step 9.
uint32_t elementLength = srcArray->length();
@@ -1857,7 +1878,8 @@ DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto)
DataViewObject*
DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
Handle<ArrayBufferObject*> arrayBuffer, JSObject* protoArg)
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* protoArg,
bool lengthTracking)
{
if (arrayBuffer->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
@@ -1866,8 +1888,18 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
MOZ_ASSERT(byteOffset <= INT32_MAX);
MOZ_ASSERT(byteLength <= INT32_MAX);
MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX);
MOZ_ASSERT(!arrayBuffer || !arrayBuffer->is<SharedArrayBufferObject>());
uint32_t bufferByteLength = arrayBuffer->byteLength();
if (byteOffset > bufferByteLength ||
(!lengthTracking && byteLength > bufferByteLength - byteOffset))
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
"1");
return nullptr;
}
if (lengthTracking)
byteLength = bufferByteLength - byteOffset;
RootedObject proto(cx, protoArg);
RootedObject obj(cx);
@@ -1891,57 +1923,72 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
}
}
// Caller should have established these preconditions, and no
// (non-self-hosted) JS code has had an opportunity to run so nothing can
// have invalidated them.
MOZ_ASSERT(byteOffset <= arrayBuffer->byteLength());
MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength());
DataViewObject& dvobj = obj->as<DataViewObject>();
dvobj.setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(byteLength));
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT,
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
: int32_t(byteLength)));
dvobj.setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*arrayBuffer));
dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset);
auto dataPointer = arrayBuffer->dataPointerEither();
dvobj.initPrivate((dataPointer + byteOffset).unwrap(/*safe - stored as private data*/));
// Include a barrier if the data view's data pointer is in the nursery, as
// is done for typed arrays.
if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(arrayBuffer->dataPointer()))
if (arrayBuffer->is<ArrayBufferObject>() &&
!IsInsideNursery(obj) &&
cx->runtime()->gc.nursery.isInside(dataPointer))
{
cx->runtime()->gc.storeBuffer.putWholeCell(obj);
}
// Verify that the private slot is at the expected place
MOZ_ASSERT(dvobj.numFixedSlots() == TypedArrayObject::DATA_SLOT);
if (!arrayBuffer->addView(cx, &dvobj))
return nullptr;
if (arrayBuffer->is<ArrayBufferObject>()) {
if (!arrayBuffer->as<ArrayBufferObject>().addView(cx, &dvobj))
return nullptr;
}
return &dvobj;
}
bool
DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr)
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr,
bool* lengthTrackingPtr)
{
if (!IsArrayBuffer(bufobj)) {
if (!IsAnyArrayBuffer(bufobj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
"DataView", "ArrayBuffer", bufobj->getClass()->name);
"DataView", "ArrayBuffer or SharedArrayBuffer",
bufobj->getClass()->name);
return false;
}
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
uint32_t byteOffset = 0;
uint32_t byteLength = buffer->byteLength();
bool isResizableOrGrowable =
(buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
(buffer->is<SharedArrayBufferObject>() &&
buffer->as<SharedArrayBufferObject>().isGrowable());
bool lengthTracking = isResizableOrGrowable && !args.hasDefined(2);
if (args.length() > 1) {
if (!ToUint32(cx, args[1], &byteOffset))
uint64_t offset;
if (!ToIndex(cx, args[1], &offset))
return false;
if (byteOffset > INT32_MAX) {
if (offset > INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
"1");
return false;
}
byteOffset = uint32_t(offset);
}
if (buffer->isDetached()) {
if (buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
@@ -1955,14 +2002,18 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
if (args.get(2).isUndefined()) {
byteLength -= byteOffset;
lengthTracking = isResizableOrGrowable;
} else {
if (!ToUint32(cx, args[2], &byteLength))
uint64_t viewByteLength;
if (!ToIndex(cx, args[2], &viewByteLength))
return false;
if (byteLength > INT32_MAX) {
lengthTracking = false;
if (viewByteLength > INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
return false;
}
byteLength = uint32_t(viewByteLength);
MOZ_ASSERT(byteOffset + byteLength >= byteOffset,
"can't overflow: both numbers are less than INT32_MAX");
@@ -1981,6 +2032,7 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
*byteOffsetPtr = byteOffset;
*byteLengthPtr = byteLength;
*lengthTrackingPtr = lengthTracking;
return true;
}
@@ -1992,7 +2044,8 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
assertSameCompartment(cx, bufobj);
uint32_t byteOffset, byteLength;
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength))
bool lengthTracking;
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength, &lengthTracking))
return false;
RootedObject proto(cx);
@@ -2000,8 +2053,9 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto);
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto,
lengthTracking);
if (!obj)
return false;
args.rval().setObject(*obj);
@@ -2041,8 +2095,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
// NB: This entails the IsArrayBuffer check
uint32_t byteOffset, byteLength;
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength))
bool lengthTracking;
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength,
&lengthTracking))
{
return false;
}
// Make sure to get the [[Prototype]] for the created view from this
// compartment.
@@ -2058,11 +2116,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
return false;
}
FixedInvokeArgs<3> args2(cx);
FixedInvokeArgs<4> args2(cx);
args2[0].set(PrivateUint32Value(byteOffset));
args2[1].set(PrivateUint32Value(byteLength));
args2[2].setObject(*proto);
args2[3].setBoolean(lengthTracking);
RootedValue fval(cx, global->createDataViewForThis());
RootedValue thisv(cx, ObjectValue(*bufobj));
@@ -2090,6 +2149,11 @@ template <typename NativeType>
/* static */ uint8_t*
DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset)
{
if (obj->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
return nullptr;
}
const size_t TypeSize = sizeof(NativeType);
if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
@@ -2194,7 +2258,7 @@ DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj,
bool isLittleEndian = args.length() >= 2 && ToBoolean(args[1]);
// Steps 6-7.
if (obj->arrayBuffer().isDetached()) {
if (obj->arrayBufferEither().isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
@@ -2293,7 +2357,7 @@ DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj,
bool isLittleEndian = args.length() >= 3 && ToBoolean(args[2]);
// Steps 7-8.
if (obj->arrayBuffer().isDetached()) {
if (obj->arrayBufferEither().isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
@@ -3118,7 +3182,13 @@ template<Value ValueGetter(DataViewObject* view)>
bool
DataViewObject::getterImpl(JSContext* cx, const CallArgs& args)
{
args.rval().set(ValueGetter(&args.thisv().toObject().as<DataViewObject>()));
Rooted<DataViewObject*> view(cx, &args.thisv().toObject().as<DataViewObject>());
if (ValueGetter != bufferValue && view->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
return false;
}
args.rval().set(ValueGetter(view));
return true;
}
+121 -20
View File
@@ -52,6 +52,8 @@ class TypedArrayObject : public NativeObject
"right buffer slot");
// Slot containing length of the view in number of typed elements.
// Length-tracking views on resizable/growable buffers store
// LENGTH_TRACKING here and compute their visible length from the buffer.
static const size_t LENGTH_SLOT = 1;
static_assert(LENGTH_SLOT == JS_TYPEDARRAYLAYOUT_LENGTH_SLOT,
"self-hosted code with burned-in constants must get the "
@@ -65,6 +67,8 @@ class TypedArrayObject : public NativeObject
static const size_t RESERVED_SLOTS = 3;
static const int32_t LENGTH_TRACKING = -1;
#ifdef DEBUG
static const uint8_t ZeroLengthArrayData = 0x4A;
#endif
@@ -139,15 +143,13 @@ class TypedArrayObject : public NativeObject
return tarr->getFixedSlot(BUFFER_SLOT);
}
static Value byteOffsetValue(TypedArrayObject* tarr) {
Value v = tarr->getFixedSlot(BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v;
return Int32Value(tarr->byteOffset());
}
static Value byteLengthValue(TypedArrayObject* tarr) {
return Int32Value(tarr->getFixedSlot(LENGTH_SLOT).toInt32() * tarr->bytesPerElement());
return Int32Value(tarr->byteLength());
}
static Value lengthValue(TypedArrayObject* tarr) {
return tarr->getFixedSlot(LENGTH_SLOT);
return Int32Value(tarr->length());
}
static bool
@@ -159,14 +161,70 @@ class TypedArrayObject : public NativeObject
JSObject* bufferObject() const {
return bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
}
bool isLengthTracking() const {
return getFixedSlot(LENGTH_SLOT).toInt32() == LENGTH_TRACKING;
}
bool hasResizableOrGrowableBuffer() const {
if (!hasBuffer())
return false;
if (isSharedMemory())
return bufferShared()->isGrowable();
return bufferUnshared()->isResizable();
}
uint32_t byteOffsetMaybeOutOfBounds() const {
Value v = getFixedSlot(BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v.toInt32();
}
uint32_t fixedLengthMaybeOutOfBounds() const {
int32_t length = getFixedSlot(LENGTH_SLOT).toInt32();
MOZ_ASSERT(length >= 0);
return length;
}
uint32_t bufferByteLength() const {
MOZ_ASSERT(hasBuffer());
if (isSharedMemory())
return bufferShared()->byteLength();
return bufferUnshared()->byteLength();
}
bool isOutOfBounds() const {
if (!hasBuffer())
return false;
if (hasDetachedBuffer())
return true;
uint32_t bufferByteLength = this->bufferByteLength();
uint32_t offset = byteOffsetMaybeOutOfBounds();
if (offset > bufferByteLength)
return true;
if (isLengthTracking())
return false;
uint32_t byteLength = fixedLengthMaybeOutOfBounds() * bytesPerElement();
return byteLength > bufferByteLength - offset;
}
uint32_t byteOffset() const {
return byteOffsetValue(const_cast<TypedArrayObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
return byteOffsetMaybeOutOfBounds();
}
uint32_t byteLength() const {
return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
return length() * bytesPerElement();
}
uint32_t length() const {
return lengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
if (!isLengthTracking()) {
if (isOutOfBounds())
return 0;
return fixedLengthMaybeOutOfBounds();
}
if (isOutOfBounds())
return 0;
uint32_t bufferByteLength = this->bufferByteLength();
uint32_t offset = byteOffsetMaybeOutOfBounds();
return (bufferByteLength - offset) / bytesPerElement();
}
bool hasInlineElements() const;
@@ -466,28 +524,26 @@ class DataViewObject : public NativeObject
defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto);
static bool getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
uint32_t *byteOffset, uint32_t* byteLength);
uint32_t *byteOffset, uint32_t* byteLength,
bool* lengthTracking);
static bool constructSameCompartment(JSContext* cx, HandleObject bufobj, const CallArgs& args);
static bool constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args);
friend bool ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args);
static DataViewObject*
create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
Handle<ArrayBufferObject*> arrayBuffer, JSObject* proto);
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* proto,
bool lengthTracking = false);
public:
static const Class class_;
static Value byteOffsetValue(DataViewObject* view) {
Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v;
return Int32Value(view->byteOffset());
}
static Value byteLengthValue(DataViewObject* view) {
Value v = view->getFixedSlot(TypedArrayObject::LENGTH_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v;
return Int32Value(view->byteLength());
}
static Value bufferValue(DataViewObject* view) {
@@ -495,21 +551,66 @@ class DataViewObject : public NativeObject
}
uint32_t byteOffset() const {
return byteOffsetValue(const_cast<DataViewObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
return byteOffsetMaybeOutOfBounds();
}
uint32_t byteLength() const {
return byteLengthValue(const_cast<DataViewObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
if (isLengthTracking())
return arrayBufferEither().byteLength() - byteOffsetMaybeOutOfBounds();
return fixedByteLengthMaybeOutOfBounds();
}
ArrayBufferObject& arrayBuffer() const {
return bufferValue(const_cast<DataViewObject*>(this)).toObject().as<ArrayBufferObject>();
ArrayBufferObjectMaybeShared& arrayBufferEither() const {
return bufferValue(const_cast<DataViewObject*>(this)).
toObject().as<ArrayBufferObjectMaybeShared>();
}
bool isSharedMemory() const {
return bufferValue(const_cast<DataViewObject*>(this)).
toObject().is<SharedArrayBufferObject>();
}
void* dataPointer() const {
return getPrivate();
}
bool isLengthTracking() const {
return getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32() ==
TypedArrayObject::LENGTH_TRACKING;
}
uint32_t byteOffsetMaybeOutOfBounds() const {
Value v = getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v.toInt32();
}
uint32_t fixedByteLengthMaybeOutOfBounds() const {
int32_t length = getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32();
MOZ_ASSERT(length >= 0);
return length;
}
bool isOutOfBounds() const {
const ArrayBufferObjectMaybeShared& buffer = arrayBufferEither();
if (buffer.isDetached())
return true;
uint32_t bufferByteLength = buffer.byteLength();
uint32_t offset = byteOffsetMaybeOutOfBounds();
if (offset > bufferByteLength)
return true;
if (isLengthTracking())
return false;
return fixedByteLengthMaybeOutOfBounds() > bufferByteLength - offset;
}
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
static bool getInt8Impl(JSContext* cx, const CallArgs& args);
+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
@@ -104,9 +104,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
@@ -19,6 +19,7 @@
#define wasm_ion_compile_h
#include "jit/MacroAssembler.h"
#include "vm/HelperThreads.h"
#include "wasm/WasmTypes.h"
namespace js {
@@ -106,6 +107,8 @@ class IonCompileTask
UniqueFuncBytes func_;
CompileMode mode_;
Maybe<FuncCompileResults> results_;
IonCompileTaskPtrVector* finishedList_;
bool failed_;
IonCompileTask(const IonCompileTask&) = delete;
IonCompileTask& operator=(const IonCompileTask&) = delete;
@@ -113,7 +116,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_;
}
@@ -143,6 +165,7 @@ class IonCompileTask
results_.reset();
lifo_.releaseAll();
mode_ = CompileMode::None;
failed_ = false;
}
};
+1 -3
View File
@@ -1442,7 +1442,6 @@ ReloadPrefsCallback(const char* pref, void* data)
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
bool weakRefs = Preferences::GetBool(JS_OPTIONS_DOT_STR "weakrefs");
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
@@ -1469,8 +1468,7 @@ ReloadPrefsCallback(const char* pref, void* data)
.setWerror(werror)
.setExtraWarnings(extraWarnings)
.setArrayProtoValues(arrayProtoValues)
.setStreams(streams)
.setWeakRefs(weakRefs);
.setStreams(streams);
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
@@ -0,0 +1,108 @@
/* 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/. */
function assertThrowsTypeError(fn) {
try {
fn();
} catch (e) {
do_check_true(e instanceof TypeError);
return;
}
do_throw("expected TypeError");
}
add_task(function* test_finalization_registry_api_surface() {
do_check_eq(typeof FinalizationRegistry, "function");
do_check_eq(FinalizationRegistry.name, "FinalizationRegistry");
do_check_eq(FinalizationRegistry.length, 1);
assertThrowsTypeError(() => FinalizationRegistry(function() {}));
assertThrowsTypeError(() => new FinalizationRegistry(1));
let registry = new FinalizationRegistry(function() {});
do_check_eq(Object.prototype.toString.call(registry), "[object FinalizationRegistry]");
do_check_eq(typeof FinalizationRegistry.prototype.register, "function");
do_check_eq(FinalizationRegistry.prototype.register.length, 2);
do_check_eq(typeof FinalizationRegistry.prototype.unregister, "function");
do_check_eq(FinalizationRegistry.prototype.unregister.length, 1);
do_check_eq(FinalizationRegistry.prototype.cleanupSome, undefined);
let desc = Object.getOwnPropertyDescriptor(FinalizationRegistry.prototype,
Symbol.toStringTag);
do_check_eq(desc.value, "FinalizationRegistry");
do_check_eq(desc.writable, false);
do_check_eq(desc.enumerable, false);
do_check_eq(desc.configurable, true);
});
add_task(function* test_register_validation_and_unregister() {
let registry = new FinalizationRegistry(function() {});
let target = {};
let token = {};
do_check_eq(registry.register(target, "held"), undefined);
do_check_eq(registry.register({}, "held", token), undefined);
do_check_eq(registry.unregister(token), true);
do_check_eq(registry.unregister({}), false);
assertThrowsTypeError(() => registry.register(1, "held"));
assertThrowsTypeError(() => registry.register(target, target));
assertThrowsTypeError(() => registry.register(target, "held", 1));
assertThrowsTypeError(() => registry.register(Symbol.for("registered"), "held"));
assertThrowsTypeError(() => registry.unregister(undefined));
do_check_eq(registry.register(Symbol("target"), "held"), undefined);
let symbolToken = Symbol("token");
do_check_eq(registry.register({}, "held", symbolToken), undefined);
do_check_eq(registry.unregister(symbolToken), true);
});
add_task(function* test_proto_from_constructor_realm() {
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
Components.utils.evalInSandbox("var newTarget = new Function();", other);
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
other.newTarget.prototype = proto;
let registry = Reflect.construct(FinalizationRegistry, [function() {}],
other.newTarget);
do_check_true(Object.getPrototypeOf(registry) ===
other.FinalizationRegistry.prototype);
}
});
add_task(function* test_cleanup_callback_after_gc() {
let cleaned = [];
let registry = new FinalizationRegistry(value => cleaned.push(value));
let token = {};
(function() {
let target = {};
registry.register(target, "held", token);
})();
for (let i = 0; i < 4 && cleaned.length == 0; i++) {
Components.utils.forceGC();
yield Promise.resolve();
}
do_check_eq(cleaned.length, 1);
do_check_eq(cleaned[0], "held");
do_check_eq(registry.unregister(token), false);
});
add_task(function* test_unregister_prevents_cleanup() {
let cleaned = [];
let registry = new FinalizationRegistry(value => cleaned.push(value));
let token = {};
(function() {
let target = {};
registry.register(target, "held", token);
})();
do_check_eq(registry.unregister(token), true);
Components.utils.forceGC();
yield Promise.resolve();
do_check_eq(cleaned.length, 0);
});
+58
View File
@@ -0,0 +1,58 @@
/* 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/. */
function assertThrowsTypeError(fn) {
try {
fn();
} catch (e) {
do_check_true(e instanceof TypeError);
return;
}
do_throw("expected TypeError");
}
add_task(function* test_weakref_api_surface() {
do_check_eq(typeof WeakRef, "function");
do_check_eq(WeakRef.name, "WeakRef");
do_check_eq(WeakRef.length, 1);
assertThrowsTypeError(() => WeakRef({}));
assertThrowsTypeError(() => new WeakRef(1));
assertThrowsTypeError(() => new WeakRef(Symbol.for("registered")));
let target = {};
let ref = new WeakRef(target);
do_check_eq(Object.prototype.toString.call(ref), "[object WeakRef]");
do_check_eq(ref.deref(), target);
do_check_eq(typeof WeakRef.prototype.deref, "function");
do_check_eq(WeakRef.prototype.deref.length, 0);
let desc = Object.getOwnPropertyDescriptor(WeakRef.prototype,
Symbol.toStringTag);
do_check_eq(desc.value, "WeakRef");
do_check_eq(desc.writable, false);
do_check_eq(desc.enumerable, false);
do_check_eq(desc.configurable, true);
});
add_task(function* test_newtarget_prototype_is_not_object() {
function newTarget() {}
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
newTarget.prototype = proto;
let ref = Reflect.construct(WeakRef, [{}], newTarget);
do_check_true(Object.getPrototypeOf(ref) === WeakRef.prototype);
}
});
add_task(function* test_proto_from_constructor_realm() {
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
Components.utils.evalInSandbox("var newTarget = new Function();", other);
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
other.newTarget.prototype = proto;
let ref = Reflect.construct(WeakRef, [{}], other.newTarget);
do_check_true(Object.getPrototypeOf(ref) === other.WeakRef.prototype);
}
});
+2
View File
@@ -71,7 +71,9 @@ support-files =
[test_import_fail.js]
[test_interposition.js]
[test_isModuleLoaded.js]
[test_finalization_registry.js]
[test_js_weak_references.js]
[test_weakref.js]
[test_onGarbageCollection-01.js]
head = head_ongc.js
[test_onGarbageCollection-02.js]
+8 -11
View File
@@ -1743,7 +1743,9 @@ nsCSSRendering::PaintBackground(const PaintBGParams& aParams)
}
static bool
IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
const nsStyleColor& aColor,
mozilla::Side aSide)
{
if (aBorder.GetComputedBorder().Side(aSide) == 0)
return true;
@@ -1765,25 +1767,20 @@ IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
return false;
StyleComplexColor color = aBorder.mBorderColor[aSide];
// We don't know the foreground color here, so if it's being used
// we must assume it might be transparent.
if (!color.IsNumericColor()) {
return false;
}
return NS_GET_A(color.mColor) == 255;
nscolor color = aColor.CalcComplexColor(aBorder.mBorderColor[aSide]);
return NS_GET_A(color) == 255;
}
/**
* Returns true if all border edges are either missing or opaque.
*/
static bool
IsOpaqueBorder(const nsStyleBorder& aBorder)
IsOpaqueBorder(const nsStyleBorder& aBorder, const nsStyleColor& aColor)
{
if (aBorder.mBorderColors)
return false;
NS_FOR_CSS_SIDES(i) {
if (!IsOpaqueBorderEdge(aBorder, i))
if (!IsOpaqueBorderEdge(aBorder, aColor, i))
return false;
}
return true;
@@ -1916,7 +1913,7 @@ nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
}
bool isSolidBorder =
aWillPaintBorder && IsOpaqueBorder(aBorder);
aWillPaintBorder && IsOpaqueBorder(aBorder, *aForFrame->StyleColor());
if (isSolidBorder && layerClip == StyleGeometryBox::Border) {
// If we have rounded corners, we need to inflate the background
// drawing area a bit to avoid seams between the border and
+14 -4
View File
@@ -4652,7 +4652,10 @@ nsFrame::GetIntrinsicSize()
/* virtual */ AspectRatio
nsFrame::GetIntrinsicRatio()
{
return AspectRatio();
const nsStylePosition* stylePos = StylePosition();
return stylePos->mAspectRatio == 0.0f
? AspectRatio()
: AspectRatio(stylePos->mAspectRatio);
}
void
@@ -4721,9 +4724,16 @@ nsFrame::ComputeSize(nsRenderingContext* aRenderingContext,
const LogicalSize& aPadding,
ComputeSizeFlags aFlags)
{
MOZ_ASSERT(!GetIntrinsicRatio(),
"Please override this method and call "
"nsFrame::ComputeSizeWithIntrinsicDimensions instead.");
AspectRatio intrinsicRatio = GetIntrinsicRatio();
if (intrinsicRatio) {
return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
GetIntrinsicSize(),
intrinsicRatio,
aCBSize, aMargin,
aBorder, aPadding,
aFlags);
}
LogicalSize result = ComputeAutoSize(aRenderingContext, aWM,
aCBSize, aAvailableISize,
aMargin, aBorder, aPadding,
+10 -9
View File
@@ -71,15 +71,16 @@ vpx_codec_set_frame_buffer_functions
vpx_codec_enc_init_multi_ver
#endif
#ifdef MOZ_AV1
aom_codec_av1_dx
aom_codec_dec_init_ver
aom_codec_decode
aom_codec_destroy
aom_codec_err_to_string
aom_codec_get_frame
aom_codec_peek_stream_info
aom_img_alloc
aom_img_free
dav1d_default_settings
dav1d_open
dav1d_parse_sequence_header
dav1d_send_data
dav1d_get_picture
dav1d_flush
dav1d_close
dav1d_data_create
dav1d_data_unref
dav1d_picture_unref
#endif
#ifdef MOZ_JXL
JxlDecoderCreate
@@ -0,0 +1,18 @@
<!doctype html>
<meta charset="utf-8">
<style>
:root {
--color: #fff;
background: var(--color);
color: var(--color);
}
div {
display: block;
width: 51px;
height: 51px;
border-radius: 50%;
background-color: #323232;
border: 10px solid var(--color);
}
</style>
<div></div>
@@ -0,0 +1,18 @@
<!doctype html>
<meta charset="utf-8">
<style>
:root {
--color: #fff;
background: var(--color);
color: var(--color);
}
div {
display: block;
width: 51px;
height: 51px;
border-radius: 50%;
background-color: #323232;
border: 10px solid currentcolor;
}
</style>
<div></div>
@@ -89,6 +89,8 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.te
# Test for antialiasing gaps between background and border
fuzzy-if(gtkWidget,1,9) fuzzy-if(winWidget&&!d2d,1,9) fuzzy-if(d2d,5,40) fuzzy-if(skiaContent,1,9) == curved-border-background-nogap.html curved-border-background-nogap-ref.html
== currentcolor-border-radius.html currentcolor-border-radius-ref.html
== color-layer-1a.html color-layer-1-ref.html
== corner-split.html corner-split-ref.svg # bug 1185636
+109 -11
View File
@@ -6,6 +6,7 @@
#include "nsCSSValue.h"
#include "nsStyleCoord.h"
#include <algorithm>
#include <math.h>
namespace mozilla {
@@ -46,6 +47,13 @@ namespace css {
* result_type aValue1, float aValue2);
*
* result_type
* MergeMinMax(nsCSSUnit aCalcFunction,
* result_type aValue1, result_type aValue2);
*
* result_type
* MergeClamp(result_type aMin, result_type aCenter, result_type aMax);
*
* result_type
* ComputeLeafValue(const input_type& aValue);
*
* float
@@ -70,6 +78,8 @@ namespace css {
* MergeAdditive for Plus and Minus
* MergeMultiplicativeL for Times_L (number * value)
* MergeMultiplicativeR for Times_R (value * number) and Divided
* MergeMinMax for Min and Max
* MergeClamp for Clamp
*/
template <class CalcOps>
static typename CalcOps::result_type
@@ -104,6 +114,25 @@ ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
float rhs = aOps.ComputeNumber(arr->Item(1));
return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
}
case eCSSUnit_Calc_Min:
case eCSSUnit_Calc_Max: {
typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
MOZ_ASSERT(arr->Count() >= 1, "unexpected length");
typename CalcOps::result_type result = ComputeCalc(arr->Item(0), aOps);
for (uint32_t i = 1; i < arr->Count(); ++i) {
typename CalcOps::result_type next = ComputeCalc(arr->Item(i), aOps);
result = aOps.MergeMinMax(CalcOps::GetUnit(aValue), result, next);
}
return result;
}
case eCSSUnit_Calc_Clamp: {
typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
MOZ_ASSERT(arr->Count() == 3, "unexpected length");
typename CalcOps::result_type min = ComputeCalc(arr->Item(0), aOps);
typename CalcOps::result_type center = ComputeCalc(arr->Item(1), aOps);
typename CalcOps::result_type max = ComputeCalc(arr->Item(2), aOps);
return aOps.MergeClamp(min, center, max);
}
default: {
return aOps.ComputeLeafValue(aValue);
}
@@ -168,6 +197,23 @@ struct BasicCoordCalcOps
}
return NSCoordSaturatingMultiply(aValue1, aValue2);
}
result_type
MergeMinMax(nsCSSUnit aCalcFunction,
result_type aValue1, result_type aValue2)
{
if (aCalcFunction == eCSSUnit_Calc_Min) {
return std::min(aValue1, aValue2);
}
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
return std::max(aValue1, aValue2);
}
result_type
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
{
return std::max(aMin, std::min(aCenter, aMax));
}
};
struct BasicFloatCalcOps
@@ -206,6 +252,23 @@ struct BasicFloatCalcOps
"unexpected unit");
return aValue1 / aValue2;
}
result_type
MergeMinMax(nsCSSUnit aCalcFunction,
result_type aValue1, result_type aValue2)
{
if (aCalcFunction == eCSSUnit_Calc_Min) {
return std::min(aValue1, aValue2);
}
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
return std::max(aValue1, aValue2);
}
result_type
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
{
return std::max(aMin, std::min(aCenter, aMax));
}
};
/**
@@ -255,8 +318,20 @@ template <class CalcOps>
static void
SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
{
aOps.Append("calc(");
nsCSSUnit unit = CalcOps::GetUnit(aValue);
if (unit == eCSSUnit_Calc) {
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
MOZ_ASSERT(array->Count() == 1, "unexpected length");
nsCSSUnit childUnit = CalcOps::GetUnit(array->Item(0));
if (childUnit == eCSSUnit_Calc_Min ||
childUnit == eCSSUnit_Calc_Max ||
childUnit == eCSSUnit_Calc_Clamp) {
SerializeCalcInternal(array->Item(0), aOps);
return;
}
}
aOps.Append("calc(");
if (unit == eCSSUnit_Calc) {
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
MOZ_ASSERT(array->Count() == 1, "unexpected length");
@@ -282,6 +357,14 @@ IsCalcMultiplicativeUnit(nsCSSUnit aUnit)
aUnit == eCSSUnit_Calc_Divided;
}
static inline bool
IsCalcMinMaxClampUnit(nsCSSUnit aUnit)
{
return aUnit == eCSSUnit_Calc_Min ||
aUnit == eCSSUnit_Calc_Max ||
aUnit == eCSSUnit_Calc_Clamp;
}
// Serialize a non-toplevel value in a calc() tree. See big comment
// above.
template <class CalcOps>
@@ -318,11 +401,7 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
if (needParens) {
aOps.Append("(");
}
if (unit == eCSSUnit_Calc_Times_L) {
aOps.AppendNumber(array->Item(0));
} else {
SerializeCalcInternal(array->Item(0), aOps);
}
SerializeCalcInternal(array->Item(0), aOps);
if (needParens) {
aOps.Append(")");
}
@@ -340,14 +419,33 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
if (needParens) {
aOps.Append("(");
}
if (unit == eCSSUnit_Calc_Times_L) {
SerializeCalcInternal(array->Item(1), aOps);
} else {
aOps.AppendNumber(array->Item(1));
}
SerializeCalcInternal(array->Item(1), aOps);
if (needParens) {
aOps.Append(")");
}
} else if (IsCalcMinMaxClampUnit(unit)) {
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
MOZ_ASSERT(unit != eCSSUnit_Calc_Clamp || array->Count() == 3,
"unexpected length");
MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp || array->Count() >= 1,
"unexpected length");
if (unit == eCSSUnit_Calc_Min) {
aOps.Append("min(");
} else if (unit == eCSSUnit_Calc_Max) {
aOps.Append("max(");
} else {
MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp, "unexpected unit");
aOps.Append("clamp(");
}
for (uint32_t i = 0; i < array->Count(); ++i) {
if (i != 0) {
aOps.Append(", ");
}
SerializeCalcInternal(array->Item(i), aOps);
}
aOps.Append(")");
} else {
aOps.AppendLeafValue(aValue);
}
+169 -5
View File
@@ -7,6 +7,8 @@
#include "mozilla/CSSStyleSheet.h"
#include <algorithm>
#include "nsIAtom.h"
#include "nsCSSRuleProcessor.h"
#include "mozilla/MemoryReporting.h"
@@ -130,6 +132,153 @@ int32_t DoCompare(Numeric a, Numeric b)
return 1;
}
struct MediaQueryTypedCalcLengthResult
{
double mValue;
int32_t mExponent;
};
static bool
EvaluateMediaQueryTypedCalcLength(nsPresContext* aPresContext,
const nsCSSValue& aValue,
MediaQueryTypedCalcLengthResult& aResult)
{
switch (aValue.GetUnit()) {
case eCSSUnit_Calc: {
nsCSSValue::Array* array = aValue.GetArrayValue();
MOZ_ASSERT(array->Count() == 1, "unexpected length");
return EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0),
aResult);
}
case eCSSUnit_Calc_Plus:
case eCSSUnit_Calc_Minus: {
nsCSSValue::Array* array = aValue.GetArrayValue();
MOZ_ASSERT(array->Count() == 2, "unexpected length");
MediaQueryTypedCalcLengthResult lhs;
MediaQueryTypedCalcLengthResult rhs;
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) ||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs) ||
lhs.mExponent != rhs.mExponent) {
return false;
}
aResult.mExponent = lhs.mExponent;
aResult.mValue = aValue.GetUnit() == eCSSUnit_Calc_Plus
? lhs.mValue + rhs.mValue
: lhs.mValue - rhs.mValue;
return true;
}
case eCSSUnit_Calc_Times_L:
case eCSSUnit_Calc_Times_R:
case eCSSUnit_Calc_Divided: {
nsCSSValue::Array* array = aValue.GetArrayValue();
MOZ_ASSERT(array->Count() == 2, "unexpected length");
MediaQueryTypedCalcLengthResult lhs;
MediaQueryTypedCalcLengthResult rhs;
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) ||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs)) {
return false;
}
if (aValue.GetUnit() == eCSSUnit_Calc_Divided) {
if (rhs.mValue == 0.0) {
return false;
}
aResult.mValue = lhs.mValue / rhs.mValue;
aResult.mExponent = lhs.mExponent - rhs.mExponent;
} else {
aResult.mValue = lhs.mValue * rhs.mValue;
aResult.mExponent = lhs.mExponent + rhs.mExponent;
}
return true;
}
case eCSSUnit_Calc_Min:
case eCSSUnit_Calc_Max:
case eCSSUnit_Calc_Clamp: {
nsCSSValue::Array* array = aValue.GetArrayValue();
MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Calc_Clamp ||
array->Count() == 3, "unexpected length");
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Calc_Clamp ||
array->Count() >= 1, "unexpected length");
MediaQueryTypedCalcLengthResult result;
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0),
result)) {
return false;
}
if (aValue.GetUnit() == eCSSUnit_Calc_Clamp) {
MediaQueryTypedCalcLengthResult center;
MediaQueryTypedCalcLengthResult max;
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1),
center) ||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(2),
max) ||
result.mExponent != center.mExponent ||
result.mExponent != max.mExponent) {
return false;
}
aResult.mExponent = result.mExponent;
aResult.mValue = std::max(result.mValue,
std::min(center.mValue, max.mValue));
return true;
}
for (uint32_t i = 1; i < array->Count(); ++i) {
MediaQueryTypedCalcLengthResult item;
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(i),
item) ||
result.mExponent != item.mExponent) {
return false;
}
result.mValue = aValue.GetUnit() == eCSSUnit_Calc_Min
? std::min(result.mValue, item.mValue)
: std::max(result.mValue, item.mValue);
}
aResult = result;
return true;
}
case eCSSUnit_Number:
aResult.mValue = aValue.GetFloatValue();
aResult.mExponent = 0;
return true;
default:
break;
}
if (!aValue.IsLengthUnit()) {
return false;
}
aResult.mValue = double(nsRuleNode::CalcLengthWithInitialFont(aPresContext,
aValue));
aResult.mExponent = 1;
return true;
}
static bool
ComputeMediaQueryTypedCalcLength(nsPresContext* aPresContext,
const nsCSSValue& aValue,
nscoord& aResult)
{
if (aValue.IsLengthUnit()) {
aResult = nsRuleNode::CalcLengthWithInitialFont(aPresContext, aValue);
return true;
}
MediaQueryTypedCalcLengthResult result;
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, aValue, result) ||
result.mExponent != 1) {
return false;
}
aResult = NSToCoordFloorClamped(result.mValue);
return true;
}
bool
nsMediaExpression::Matches(nsPresContext *aPresContext,
const nsCSSValue& aActualValue) const
@@ -161,11 +310,15 @@ nsMediaExpression::Matches(nsPresContext *aPresContext,
case nsMediaFeature::eLength:
{
NS_ASSERTION(actual.IsLengthUnit(), "bad actual value");
NS_ASSERTION(required.IsLengthUnit(), "bad required value");
NS_ASSERTION(required.IsLengthUnit() || required.IsCalcUnit(),
"bad required value");
nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont(
aPresContext, actual);
nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont(
aPresContext, required);
nscoord requiredCoord;
if (!ComputeMediaQueryTypedCalcLength(aPresContext, required,
requiredCoord)) {
return false;
}
cmp = DoCompare(actualCoord, requiredCoord);
}
break;
@@ -283,6 +436,10 @@ nsMediaExpression::Matches(nsPresContext *aPresContext,
return cmp != 1;
case nsMediaExpression::eEqual:
return cmp == 0;
case nsMediaExpression::eMinExclusive:
return cmp == 1;
case nsMediaExpression::eMaxExclusive:
return cmp == -1;
}
NS_NOTREACHED("unexpected mRange");
return false;
@@ -487,10 +644,17 @@ nsMediaQuery::AppendToString(nsAString& aString) const
aString.Append(nsDependentAtomString(*feature->mName));
if (expr.mValue.GetUnit() != eCSSUnit_Null) {
aString.AppendLiteral(": ");
if (expr.mRange == nsMediaExpression::eMinExclusive) {
aString.AppendLiteral(" > ");
} else if (expr.mRange == nsMediaExpression::eMaxExclusive) {
aString.AppendLiteral(" < ");
} else {
aString.AppendLiteral(": ");
}
switch (feature->mValueType) {
case nsMediaFeature::eLength:
NS_ASSERTION(expr.mValue.IsLengthUnit(), "bad unit");
NS_ASSERTION(expr.mValue.IsLengthUnit() || expr.mValue.IsCalcUnit(),
"bad unit");
// Use 'width' as a property that takes length values
// written in the normal way.
expr.mValue.AppendToString(eCSSProperty_width, aString,

Some files were not shown because too many files have changed in this diff Show More